factory_hash_map/
factory_hash_map.rs

1use gtk::prelude::{
2    BoxExt, ButtonExt, EntryBufferExtManual, EntryExt, GtkWindowExt, OrientableExt, WidgetExt,
3};
4use relm4::factory::{FactoryComponent, FactoryHashMap, FactorySender};
5use relm4::{ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent};
6
7#[derive(Debug)]
8struct Counter {
9    name: String,
10    value: u8,
11}
12
13#[derive(Debug)]
14enum CounterOutput {}
15
16#[relm4::factory]
17impl FactoryComponent for Counter {
18    type Init = u8;
19    type Input = ();
20    type Output = CounterOutput;
21    type CommandOutput = ();
22    type ParentWidget = gtk::Stack;
23    type Index = String;
24
25    view! {
26        #[root]
27        root = gtk::Box {
28            set_orientation: gtk::Orientation::Horizontal,
29            set_halign: gtk::Align::Center,
30            set_spacing: 10,
31            set_margin_all: 12,
32
33            #[name(label)]
34            gtk::Label {
35                set_use_markup: true,
36                #[watch]
37                set_label: &format!("<b>Counter value: {}</b>", self.value),
38                set_width_chars: 3,
39            },
40
41        },
42        #[local_ref]
43        returned_widget -> gtk::StackPage {
44            set_name: &self.name,
45            set_title: &self.name,
46        }
47    }
48
49    fn init_model(value: Self::Init, index: &Self::Index, _sender: FactorySender<Self>) -> Self {
50        Self {
51            name: index.clone(),
52            value,
53        }
54    }
55
56    fn shutdown(&mut self, _widgets: &mut Self::Widgets, _output: relm4::Sender<Self::Output>) {
57        println!("Counter with value {} was destroyed", self.value);
58    }
59}
60
61struct App {
62    created_widgets: u8,
63    counters: FactoryHashMap<String, Counter>,
64    entry_buffer: gtk::EntryBuffer,
65}
66
67#[derive(Debug)]
68enum AppMsg {
69    UpdateView,
70    AddCounter,
71    Increment(String),
72    Decrement(String),
73    RemoveCounter(String),
74}
75
76#[relm4::component]
77impl SimpleComponent for App {
78    type Init = u8;
79    type Input = AppMsg;
80    type Output = ();
81
82    view! {
83        gtk::Window {
84            set_title: Some("Factory example"),
85            set_default_size: (300, 100),
86
87            gtk::Box {
88                set_orientation: gtk::Orientation::Vertical,
89                set_spacing: 5,
90                set_margin_all: 5,
91
92                gtk::StackSwitcher {
93                    set_stack: Some(counter_stack),
94                },
95
96                gtk::Entry {
97                    set_buffer: &model.entry_buffer,
98                    connect_activate => AppMsg::AddCounter,
99                },
100
101                #[name(add_button)]
102                gtk::Button {
103                    set_label: "Increment",
104                    #[watch]
105                    set_sensitive: counter_stack.visible_child().is_some(),
106                    connect_clicked[sender, counter_stack] => move |_| {
107                        if let Some(name) = counter_stack.visible_child_name() {
108                            sender.input(AppMsg::Increment(name.into()));
109                        }
110                    },
111                },
112
113                #[name(remove_button)]
114                gtk::Button {
115                    set_label: "Decrement",
116                    #[watch]
117                    set_sensitive: counter_stack.visible_child().is_some(),
118                    connect_clicked[sender, counter_stack] => move |_| {
119                        if let Some(name) = counter_stack.visible_child_name() {
120                            sender.input(AppMsg::Decrement(name.into()));
121                        }
122                    },
123                },
124
125                gtk::Button {
126                    set_label: "Remove counter",
127                    #[watch]
128                    set_sensitive: counter_stack.visible_child().is_some(),
129                    connect_clicked[sender, counter_stack] => move |_| {
130                        if let Some(name) = counter_stack.visible_child_name() {
131                            sender.input(AppMsg::RemoveCounter(name.into()));
132                        }
133                    },
134                },
135
136                #[local_ref]
137                counter_stack -> gtk::Stack {
138                    connect_visible_child_notify => AppMsg::UpdateView,
139                }
140            }
141        }
142    }
143
144    fn init(
145        counter: Self::Init,
146        root: Self::Root,
147        sender: ComponentSender<Self>,
148    ) -> ComponentParts<Self> {
149        let counters = FactoryHashMap::builder().launch_default().detach();
150
151        let model = App {
152            created_widgets: counter,
153            counters,
154            entry_buffer: gtk::EntryBuffer::default(),
155        };
156
157        let counter_stack = model.counters.widget();
158        let widgets = view_output!();
159
160        ComponentParts { model, widgets }
161    }
162
163    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
164        match msg {
165            AppMsg::AddCounter => {
166                let index = self.entry_buffer.text().to_string();
167                if !index.is_empty() {
168                    self.counters.insert(index.clone(), self.created_widgets);
169                    // Change focus to the currently created element
170                    self.counters.set_visible(&index);
171                    self.created_widgets = self.created_widgets.wrapping_add(1);
172                }
173            }
174            AppMsg::Increment(key) => {
175                let mut elem = self.counters.get_mut(&key).unwrap();
176                elem.value = elem.value.saturating_add(1);
177            }
178            AppMsg::Decrement(key) => {
179                let mut elem = self.counters.get_mut(&key).unwrap();
180                elem.value = elem.value.saturating_sub(1);
181            }
182            AppMsg::RemoveCounter(key) => {
183                self.counters.remove(&key);
184            }
185            AppMsg::UpdateView => (),
186        }
187    }
188}
189
190fn main() {
191    let app = RelmApp::new("relm4.example.factory");
192    app.run::<App>(0);
193}