use gtk::prelude::{ BoxExt, ButtonExt, EntryBufferExtManual, EntryExt, GtkWindowExt, OrientableExt, WidgetExt, }; use relm4::factory::{FactoryComponent, FactoryHashMap, FactorySender}; use relm4::{ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent}; #[derive(Debug)] struct Counter { name: String, value: u8, } #[derive(Debug)] enum CounterOutput {} #[relm4::factory] impl FactoryComponent for Counter { type Init = u8; type Input = (); type Output = CounterOutput; type CommandOutput = (); type ParentWidget = gtk::Stack; type Index = String; view! { #[root] root = gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_halign: gtk::Align::Center, set_spacing: 10, set_margin_all: 12, #[name(label)] gtk::Label { set_use_markup: true, #[watch] set_label: &format!("Counter value: {}", self.value), set_width_chars: 3, }, }, #[local_ref] returned_widget -> gtk::StackPage { set_name: &self.name, set_title: &self.name, } } fn init_model(value: Self::Init, index: &Self::Index, _sender: FactorySender) -> Self { Self { name: index.clone(), value, } } fn shutdown(&mut self, _widgets: &mut Self::Widgets, _output: relm4::Sender) { println!("Counter with value {} was destroyed", self.value); } } struct App { created_widgets: u8, counters: FactoryHashMap, entry_buffer: gtk::EntryBuffer, } #[derive(Debug)] enum AppMsg { UpdateView, AddCounter, Increment(String), Decrement(String), RemoveCounter(String), } #[relm4::component] impl SimpleComponent for App { type Init = u8; type Input = AppMsg; type Output = (); view! { gtk::Window { set_title: Some("Factory example"), set_default_size: (300, 100), gtk::Box { set_orientation: gtk::Orientation::Vertical, set_spacing: 5, set_margin_all: 5, gtk::StackSwitcher { set_stack: Some(counter_stack), }, gtk::Entry { set_buffer: &model.entry_buffer, connect_activate => AppMsg::AddCounter, }, #[name(add_button)] gtk::Button { set_label: "Increment", #[watch] set_sensitive: counter_stack.visible_child().is_some(), connect_clicked[sender, counter_stack] => move |_| { if let Some(name) = counter_stack.visible_child_name() { sender.input(AppMsg::Increment(name.into())); } }, }, #[name(remove_button)] gtk::Button { set_label: "Decrement", #[watch] set_sensitive: counter_stack.visible_child().is_some(), connect_clicked[sender, counter_stack] => move |_| { if let Some(name) = counter_stack.visible_child_name() { sender.input(AppMsg::Decrement(name.into())); } }, }, gtk::Button { set_label: "Remove counter", #[watch] set_sensitive: counter_stack.visible_child().is_some(), connect_clicked[sender, counter_stack] => move |_| { if let Some(name) = counter_stack.visible_child_name() { sender.input(AppMsg::RemoveCounter(name.into())); } }, }, #[local_ref] counter_stack -> gtk::Stack { connect_visible_child_notify => AppMsg::UpdateView, } } } } fn init( counter: Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts { let counters = FactoryHashMap::builder().launch_default().detach(); let model = App { created_widgets: counter, counters, entry_buffer: gtk::EntryBuffer::default(), }; let counter_stack = model.counters.widget(); let widgets = view_output!(); ComponentParts { model, widgets } } fn update(&mut self, msg: Self::Input, _sender: ComponentSender) { match msg { AppMsg::AddCounter => { let index = self.entry_buffer.text().to_string(); if !index.is_empty() { self.counters.insert(index, self.created_widgets); self.created_widgets = self.created_widgets.wrapping_add(1); } } AppMsg::Increment(key) => { let mut elem = self.counters.get_mut(&key).unwrap(); elem.value = elem.value.saturating_add(1); } AppMsg::Decrement(key) => { let mut elem = self.counters.get_mut(&key).unwrap(); elem.value = elem.value.saturating_sub(1); } AppMsg::RemoveCounter(key) => { self.counters.remove(&key); } AppMsg::UpdateView => (), } } } fn main() { let app = RelmApp::new("relm4.example.factory"); app.run::(0); }