Skip to content

Instantly share code, notes, and snippets.

@mskorkowski
Last active September 14, 2021 02:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mskorkowski/ae88013c4d19b6479270aeeaa08e8416 to your computer and use it in GitHub Desktop.
Save mskorkowski/ae88013c4d19b6479270aeeaa08e8416 to your computer and use it in GitHub Desktop.
Reuse the component twice in relm4

This gist shows issue with reusing components multiple times

Story

I'm creating an application where I need to show some data of the same type twice in different context on the same screen. I've created fairly complex component to show the data. I can't use factory!/list to create them because they do not have common parts around them.

I've simplified the case to use just two buttons "Left" and "Right" to show the isse (main.rs). I've created simplest component which just creates a button with label and main window which creates this component twice.

Solution I've came up with uses strategy pattern over empty structs so it can be passed around as part of type system. (main-initiator.rs).

To run the code you can use the attached Cargo.toml and copy one of the files to the main.rs and build it.

[package]
name = "sample"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gtk = { version = "0.2", package = "gtk4" }
relm4 = "0.1.0"
relm4-macros = "0.1.0"
relm4-components = "0.1.0"
///
/// Solution I came up with
///
///
///
use std::marker::PhantomData;
use gtk::prelude::ButtonExt;
use gtk::prelude::BoxExt;
use gtk::prelude::OrientableExt;
use gtk::prelude::GtkWindowExt;
use relm4::*;
// Provides interface for button initialization strategy
//
// Extra trait with only static methods
trait ButtonInitiator<Parent>
where
Parent: ButtonParentModel
{
fn get_label(parent_model: &Parent) -> String;
}
// Allows to initialize left button
struct LeftButtonInitiator{}
impl<Parent> ButtonInitiator<Parent> for LeftButtonInitiator
where
Parent: ButtonParentModel
{
fn get_label(parent_model: &Parent) -> String {
parent_model.get_left()
}
}
// Allows to initialize right button
struct RightButtonInitiator{}
impl<Parent> ButtonInitiator<Parent> for RightButtonInitiator
where
Parent: ButtonParentModel
{
fn get_label(parent_model: &Parent) -> String {
parent_model.get_right()
}
}
struct ButtonModel<Initiator, Parent>
where
Parent: ButtonParentModel,
Initiator: ButtonInitiator<Parent>,
{
label: String,
parent: PhantomData<*const Parent>,
initiator: PhantomData<*const Initiator>,
}
enum ButtonMsg{}
impl<Initiator, Parent> Model for ButtonModel<Initiator, Parent>
where
Parent: ButtonParentModel,
Initiator: ButtonInitiator<Parent>,
{
type Msg = ButtonMsg;
type Widgets = ButtonWidgets;
type Components = ();
}
trait ButtonParentModel: Model {
fn get_left(&self) -> String;
fn get_right(&self) -> String;
}
impl<Initiator, Parent> ComponentUpdate<Parent> for ButtonModel<Initiator, Parent>
where
Parent: ButtonParentModel,
Initiator: ButtonInitiator<Parent>
{
fn init_model(parent_model: &Parent) -> Self {
Self {
label: Initiator::get_label(parent_model), //Use initiator to select proper value from the model
parent: PhantomData,
initiator: PhantomData
}
}
fn update(
&mut self,
_msg: ButtonMsg,
_components: &(),
_sender: Sender<ButtonMsg>,
_parent_sender: Sender<Parent::Msg>
) {}
}
#[relm4_macros::widget]
impl<Initiator, Parent> Widgets<ButtonModel<Initiator,Parent>, Parent> for ButtonWidgets
where
Parent: ButtonParentModel,
Initiator: ButtonInitiator<Parent>
{
view! {
gtk::Button{
set_label: &model.label
}
}
}
enum AppMsg {}
struct AppModel {
left: String,
right: String,
}
impl AppModel {
fn new() -> Self {
AppModel{
left: String::from("Left"),
right: String::from("Right"),
}
}
}
impl Model for AppModel {
type Msg = AppMsg;
type Widgets = AppWidgets;
type Components = AppComponents;
}
impl ButtonParentModel for AppModel {
fn get_left(&self) -> String {
self.left.clone()
}
fn get_right(&self) -> String {
self.right.clone()
}
}
struct AppComponents {
left: RelmComponent<ButtonModel<LeftButtonInitiator, AppModel>, AppModel>,
right: RelmComponent<ButtonModel<RightButtonInitiator, AppModel>, AppModel>,
}
impl Components<AppModel> for AppComponents {
fn init_components(
parent_model: &AppModel,
parent_widgets: &AppWidgets,
parent_sender: Sender<AppMsg>,
) -> Self {
AppComponents {
left: RelmComponent::new(parent_model, parent_widgets, parent_sender.clone()),
right: RelmComponent::new(parent_model, parent_widgets, parent_sender)
}
}
}
impl AppUpdate for AppModel {
fn update(&mut self, _msg: AppMsg, _components: &AppComponents, _sender: Sender<AppMsg>) -> bool {
true
}
}
#[relm4_macros::widget]
impl Widgets<AppModel, ()> for AppWidgets {
view! {
main_window = gtk::ApplicationWindow {
set_child = Some(&gtk::Box) {
set_orientation: gtk::Orientation::Horizontal,
append: component!(components.left.root_widget()),
append: component!(components.right.root_widget()),
}
}
}
}
fn main() {
let model = AppModel::new();
let relm = RelmApp::new(model);
relm.run();
}
///
/// Issue showcase
///
///
///
use gtk::prelude::ButtonExt;
use gtk::prelude::BoxExt;
use gtk::prelude::OrientableExt;
use gtk::prelude::GtkWindowExt;
use relm4::*;
struct ButtonModel {
label: String,
}
enum ButtonMsg{}
impl Model for ButtonModel {
type Msg = ButtonMsg;
type Widgets = ButtonWidgets;
type Components = ();
}
trait ButtonParentModel: Model {
fn get_label(&self) -> String;
}
impl<Parent> ComponentUpdate<Parent> for ButtonModel
where
Parent: ButtonParentModel,
{
fn init_model(parent_model: &Parent) -> Self {
Self {
label: parent_model.get_label()
}
}
fn update(
&mut self,
_msg: ButtonMsg,
_components: &(),
_sender: Sender<ButtonMsg>,
_parent_sender: Sender<Parent::Msg>
) {}
}
#[relm4_macros::widget]
impl<Parent> Widgets<ButtonModel, Parent> for ButtonWidgets
where
Parent: ButtonParentModel
{
view! {
gtk::Button{
set_label: &model.label
}
}
}
enum AppMsg {}
struct AppModel {
left: String,
right: String,
}
impl AppModel {
fn new() -> Self {
AppModel{
left: String::from("Left"),
right: String::from("Right"),
}
}
}
impl Model for AppModel {
type Msg = AppMsg;
type Widgets = AppWidgets;
type Components = AppComponents;
}
impl ButtonParentModel for AppModel {
fn get_label(&self) -> String {
String::from("???")
}
}
struct AppComponents {
left: RelmComponent<ButtonModel, AppModel>,
right: RelmComponent<ButtonModel, AppModel>,
}
impl Components<AppModel> for AppComponents {
fn init_components(
parent_model: &AppModel,
parent_widgets: &AppWidgets,
parent_sender: Sender<AppMsg>,
) -> Self {
AppComponents {
left: RelmComponent::new(parent_model, parent_widgets, parent_sender.clone()),
right: RelmComponent::new(parent_model, parent_widgets, parent_sender)
}
}
}
impl AppUpdate for AppModel {
fn update(&mut self, _msg: AppMsg, _components: &AppComponents, _sender: Sender<AppMsg>) -> bool {
true
}
}
#[relm4_macros::widget]
impl Widgets<AppModel, ()> for AppWidgets {
view! {
main_window = gtk::ApplicationWindow {
set_child = Some(&gtk::Box) {
set_orientation: gtk::Orientation::Horizontal,
append: component!(components.left.root_widget()),
append: component!(components.right.root_widget()),
}
}
}
}
fn main() {
let model = AppModel::new();
let relm = RelmApp::new(model);
relm.run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment