FactoryVecDeque

Struct FactoryVecDeque 

Source
pub struct FactoryVecDeque<C>
where C: FactoryComponent<Index = DynamicIndex>,
{ /* private fields */ }
Expand description

A container similar to VecDeque that can be used to store data associated with components that implement FactoryComponent.

To access mutable methods of the factory, create a guard using Self::guard.

Implementations§

Source§

impl<C> FactoryVecDeque<C>
where C: FactoryComponent<Index = DynamicIndex>,

Source

pub fn builder() -> FactoryVecDequeBuilder<C>

Creates a builder for making a FactoryVecDeque.

Examples found in repository?
relm4/examples/state_management.rs (line 164)
161    fn init_model(_name: Self::Init, index: &DynamicIndex, sender: FactorySender<Self>) -> Self {
162        let task_index = index.clone();
163
164        let tags = FactoryVecDeque::builder().launch_default().forward(
165            sender.output_sender(),
166            move |output| match output {
167                TagOutput::Delete(tag_index) => {
168                    TaskOutput::DeleteTag(task_index.clone(), tag_index)
169                }
170            },
171        );
172
173        Self {
174            name: "".into(),
175            tags,
176        }
177    }
178}
179
180#[derive(Debug)]
181struct Tag {
182    name: String,
183}
184
185#[derive(Debug)]
186enum TagInput {}
187
188#[derive(Debug)]
189enum TagOutput {
190    Delete(DynamicIndex),
191}
192
193#[relm4::factory]
194impl FactoryComponent for Tag {
195    type Init = String;
196    type Input = TagInput;
197    type Output = TagOutput;
198    type CommandOutput = ();
199    type ParentWidget = gtk::Box;
200
201    view! {
202        gtk::MenuButton {
203            #[watch]
204            set_label: &self.name,
205
206            #[wrap(Some)]
207            set_popover = &gtk::Popover {
208                gtk::Button {
209                    set_label: "Delete",
210
211                    connect_clicked[sender, index] => move |_| {
212                        sender.output(TagOutput::Delete(index.clone())).unwrap();
213                    }
214                }
215            }
216        }
217    }
218
219    fn init_model(name: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
220        Self { name }
221    }
222}
223
224/// The document is a headless component which holds and manages the data model.
225/// It receives input events FROM the App to update the data model.
226/// When updates to the model occur, it sends output events TO the App.
227///
228/// The document's interface is just input and output events. As a result you have a lot of freedom
229/// in how you choose to store the data model within the component, which backing store you use
230/// (such as the file system, a database, or a Web API), and how you synchronise to the backing
231/// store (e.g. manual save/load control, auto-saving on each change, batching up changes before
232/// syncing, and so on).
233struct Document {
234    /// The application data model.
235    /// In this case we have just stored the whole thing in memory because our requirements are
236    /// simple. In a real app you might choose a more elaborate approach.
237    model: Model,
238}
239
240#[derive(Default, Serialize, Deserialize)]
241struct TagModel {
242    name: String,
243}
244#[derive(Default, Serialize, Deserialize)]
245struct TaskModel {
246    name: String,
247    tags: Vec<TagModel>,
248}
249#[derive(Default, Serialize, Deserialize)]
250struct Model {
251    tasks: Vec<TaskModel>,
252}
253
254#[derive(Debug)]
255enum DocumentInput {
256    // extra operations on the document itself (in this case, related to file I/O)
257    Open(PathBuf),
258    Save(PathBuf),
259
260    // events related to the model that the document stores
261    Clear,
262    AddTask,
263    DeleteTask(DynamicIndex),
264    ChangeTaskName(DynamicIndex, String),
265    AddTag(DynamicIndex, String),
266    DeleteTag(DynamicIndex, DynamicIndex),
267}
268
269#[derive(Debug)]
270enum DocumentOutput {
271    Cleared,
272    AddedTask,
273    DeletedTask(usize),
274    ChangedTaskName(usize, String),
275    AddedTag(usize, String),
276    DeletedTag(usize, usize),
277}
278
279impl Worker for Document {
280    type Init = ();
281    type Input = DocumentInput;
282    type Output = DocumentOutput;
283
284    fn init(_init: Self::Init, _sender: ComponentSender<Self>) -> Self {
285        let model = Model::default();
286        Self { model }
287    }
288
289    fn update(&mut self, input: DocumentInput, sender: ComponentSender<Self>) {
290        match input {
291            DocumentInput::Save(path) => {
292                println!("Save as JSON to {path:?}");
293
294                // TODO in a real app you would report any errors from saving the document
295                if let Ok(json) = serde_json::to_string(&self.model) {
296                    std::fs::write(path, json).unwrap();
297                }
298            }
299            DocumentInput::Open(path) => {
300                println!("Open tasks document at {path:?}");
301
302                if let Ok(json) = std::fs::read_to_string(path)
303                    && let Ok(new_model) = serde_json::from_str(&json)
304                {
305                    // update the data model
306                    self.model = new_model;
307
308                    // refresh the view from the data model
309                    let _ = sender.output(DocumentOutput::Cleared);
310
311                    for (task_index, task) in self.model.tasks.iter().enumerate() {
312                        let _ = sender.output(DocumentOutput::AddedTask);
313
314                        let task_name = task.name.clone();
315                        let _ =
316                            sender.output(DocumentOutput::ChangedTaskName(task_index, task_name));
317
318                        for tag in &task.tags {
319                            let tag_name = tag.name.clone();
320                            let _ = sender.output(DocumentOutput::AddedTag(task_index, tag_name));
321                        }
322                    }
323                }
324            }
325            DocumentInput::Clear => {
326                self.model.tasks.clear();
327
328                let _ = sender.output(DocumentOutput::Cleared);
329            }
330            DocumentInput::AddTask => {
331                self.model.tasks.push(TaskModel::default());
332
333                let _ = sender.output(DocumentOutput::AddedTask);
334            }
335            DocumentInput::DeleteTask(index) => {
336                self.model.tasks.remove(index.current_index());
337
338                let _ = sender.output(DocumentOutput::DeletedTask(index.current_index()));
339            }
340            DocumentInput::ChangeTaskName(index, name) => {
341                if let Some(task) = self.model.tasks.get_mut(index.current_index()) {
342                    task.name.clone_from(&name);
343                }
344
345                // We don't technically need to send an event, because gtk::Entry updates itself
346                // this is just to make the example consistent.
347                let _ = sender.output(DocumentOutput::ChangedTaskName(index.current_index(), name));
348            }
349            DocumentInput::AddTag(task_index, name) => {
350                if let Some(task) = self.model.tasks.get_mut(task_index.current_index()) {
351                    task.tags.push(TagModel { name: name.clone() })
352                }
353
354                let _ = sender.output(DocumentOutput::AddedTag(task_index.current_index(), name));
355            }
356            DocumentInput::DeleteTag(task_index, tag_index) => {
357                if let Some(task) = self.model.tasks.get_mut(task_index.current_index()) {
358                    task.tags.remove(tag_index.current_index());
359                }
360
361                let _ = sender.output(DocumentOutput::DeletedTag(
362                    task_index.current_index(),
363                    tag_index.current_index(),
364                ));
365            }
366        }
367    }
368}
369
370/// The App is at the top level.
371/// It acts as a bridge between the view and the document, forwarding events between them.
372struct App {
373    view: FactoryVecDeque<Task>,
374    document: Controller<Document>,
375    save_dialog: Controller<SaveDialog>,
376    open_dialog: Controller<OpenDialog>,
377}
378
379#[derive(Debug)]
380enum AppInput {
381    Clear,
382    Cleared,
383
384    AddTask,
385    AddedTask,
386
387    DeleteTask(DynamicIndex),
388    DeletedTask(usize),
389
390    ChangeTaskName(DynamicIndex, String),
391    ChangedTaskName(usize, String),
392
393    AddTag(DynamicIndex, String),
394    AddedTag(usize, String),
395
396    DeleteTag(DynamicIndex, DynamicIndex),
397    DeletedTag(usize, usize),
398
399    // No-op event for when load/save dialogs result in Cancel
400    None,
401    Open,
402    OpenResponse(PathBuf),
403    Save,
404    SaveResponse(PathBuf),
405}
406
407#[relm4::component]
408impl SimpleComponent for App {
409    type Init = ();
410    type Input = AppInput;
411    type Output = ();
412
413    view! {
414        main_window = gtk::ApplicationWindow {
415            set_width_request: 360,
416            set_title: Some("Tasks"),
417
418            gtk::Box {
419                set_orientation: gtk::Orientation::Vertical,
420
421                gtk::HeaderBar {
422                    set_show_title_buttons: false,
423
424                    #[wrap(Some)]
425                    set_title_widget = &gtk::Label {
426                        set_text: ""
427                    },
428
429                    pack_start = &gtk::Button {
430                        set_icon_name: "plus",
431                        set_tooltip: "Add Task",
432
433                        connect_clicked[sender] => move |_| {
434                            sender.input(AppInput::AddTask);
435                        }
436                    },
437
438                    pack_end = &gtk::Button {
439                        set_label: "Save",
440                        connect_clicked => AppInput::Save,
441                    },
442                    pack_end = &gtk::Button {
443                        set_label: "Open",
444                        connect_clicked => AppInput::Open,
445                    },
446                },
447
448                gtk::ScrolledWindow {
449                    set_hscrollbar_policy: gtk::PolicyType::Never,
450                    set_min_content_height: 360,
451                    set_vexpand: true,
452
453                    #[local_ref]
454                    task_list_box -> gtk::ListBox {
455                        set_selection_mode: gtk::SelectionMode::None,
456                    }
457                },
458
459                gtk::Box {
460                    set_hexpand: true,
461                    set_spacing: DEFAULT_SPACING,
462                    set_orientation: gtk::Orientation::Horizontal,
463
464                    gtk::Label {
465                        set_text: "Press Enter after editing task names",
466                        set_hexpand: true,
467                        set_xalign: XALIGN_CENTER,
468                    },
469
470                    gtk::Button {
471                        set_icon_name: "edit-delete",
472                        set_tooltip: "Delete All Tasks",
473                        add_css_class: CSS_CLASS_DESTRUCTIVE_ACTION,
474
475                        connect_clicked[sender] => move |_| {
476                            sender.input(AppInput::Clear);
477                        }
478                    }
479                }
480            }
481        }
482    }
483
484    fn update(&mut self, msg: AppInput, _sender: ComponentSender<Self>) {
485        match msg {
486            AppInput::Clear => {
487                self.document.emit(DocumentInput::Clear);
488            }
489            AppInput::Cleared => {
490                self.view.guard().clear();
491            }
492            AppInput::AddTask => {
493                self.document.emit(DocumentInput::AddTask);
494            }
495            AppInput::AddedTask => {
496                self.view.guard().push_back(());
497            }
498            AppInput::DeleteTask(index) => {
499                self.document.emit(DocumentInput::DeleteTask(index));
500            }
501            AppInput::DeletedTask(index) => {
502                self.view.guard().remove(index);
503            }
504            AppInput::ChangeTaskName(index, name) => {
505                self.document
506                    .emit(DocumentInput::ChangeTaskName(index, name));
507            }
508            AppInput::ChangedTaskName(index, name) => {
509                self.view.guard().send(index, TaskInput::ChangedName(name));
510            }
511            AppInput::AddTag(index, name) => {
512                self.document.emit(DocumentInput::AddTag(index, name));
513            }
514            AppInput::AddedTag(index, name) => {
515                self.view.guard().send(index, TaskInput::AddedTag(name));
516            }
517            AppInput::DeleteTag(task_index, tag_index) => {
518                self.document
519                    .emit(DocumentInput::DeleteTag(task_index, tag_index));
520            }
521            AppInput::DeletedTag(task_index, tag_index) => {
522                self.view
523                    .guard()
524                    .send(task_index, TaskInput::DeletedTag(tag_index));
525            }
526            AppInput::None => {}
527            AppInput::Save => {
528                let name = "tasks.json".into();
529                self.save_dialog.emit(SaveDialogMsg::SaveAs(name));
530            }
531            AppInput::SaveResponse(path) => {
532                self.document.emit(DocumentInput::Save(path));
533            }
534            AppInput::Open => {
535                self.open_dialog.emit(OpenDialogMsg::Open);
536            }
537            AppInput::OpenResponse(path) => {
538                self.document.emit(DocumentInput::Open(path));
539            }
540        }
541    }
542
543    fn init(
544        _: Self::Init,
545        root: Self::Root,
546        sender: ComponentSender<Self>,
547    ) -> ComponentParts<Self> {
548        let view =
549            FactoryVecDeque::builder()
550                .launch_default()
551                .forward(sender.input_sender(), |msg| match msg {
552                    TaskOutput::Delete(index) => AppInput::DeleteTask(index),
553                    TaskOutput::Name(index, name) => AppInput::ChangeTaskName(index, name),
554                    TaskOutput::AddTag(index, name) => AppInput::AddTag(index, name),
555                    TaskOutput::DeleteTag(task_index, tag_index) => {
556                        AppInput::DeleteTag(task_index, tag_index)
557                    }
558                });
559
560        let document =
561            Document::builder()
562                .launch(())
563                .forward(sender.input_sender(), |msg| match msg {
564                    DocumentOutput::Cleared => AppInput::Cleared,
565                    DocumentOutput::DeletedTask(index) => AppInput::DeletedTask(index),
566                    DocumentOutput::DeletedTag(task_index, tag_index) => {
567                        AppInput::DeletedTag(task_index, tag_index)
568                    }
569                    DocumentOutput::AddedTask => AppInput::AddedTask,
570                    DocumentOutput::AddedTag(index, name) => AppInput::AddedTag(index, name),
571                    DocumentOutput::ChangedTaskName(index, name) => {
572                        AppInput::ChangedTaskName(index, name)
573                    }
574                });
575
576        let save_dialog = SaveDialog::builder()
577            .transient_for_native(&root)
578            .launch(SaveDialogSettings {
579                create_folders: true,
580                accept_label: "Save".into(),
581                cancel_label: "Cancel".into(),
582                is_modal: true,
583                filters: tasks_filename_filters(),
584            })
585            .forward(sender.input_sender(), |response| match response {
586                SaveDialogResponse::Accept(path) => AppInput::SaveResponse(path),
587                SaveDialogResponse::Cancel => AppInput::None,
588            });
589
590        let open_dialog = OpenDialog::builder()
591            .transient_for_native(&root)
592            .launch(OpenDialogSettings {
593                create_folders: false,
594                folder_mode: false,
595                cancel_label: "Cancel".into(),
596                accept_label: "Open".into(),
597                is_modal: true,
598                filters: tasks_filename_filters(),
599            })
600            .forward(sender.input_sender(), |response| match response {
601                OpenDialogResponse::Accept(path) => AppInput::OpenResponse(path),
602                OpenDialogResponse::Cancel => AppInput::None,
603            });
604
605        let app = App {
606            view,
607            document,
608            open_dialog,
609            save_dialog,
610        };
611
612        let task_list_box = app.view.widget();
613        let widgets = view_output!();
614
615        ComponentParts {
616            model: app,
617            widgets,
618        }
619    }
More examples
Hide additional examples
relm4/examples/entry.rs (line 84)
77    fn init(
78        _init: Self::Init,
79        root: Self::Root,
80        sender: ComponentSender<Self>,
81    ) -> ComponentParts<Self> {
82        let factory_box = gtk::Box::default();
83
84        let counters = FactoryVecDeque::builder()
85            .launch(factory_box.clone())
86            .forward(sender.input_sender(), |output| output);
87
88        let model = App {
89            counters,
90            created_counters: 0,
91            entry: gtk::EntryBuffer::default(),
92        };
93
94        let widgets = view_output!();
95
96        ComponentParts { model, widgets }
97    }
relm4/examples/to_do.rs (line 146)
140    fn init(
141        _: Self::Init,
142        root: Self::Root,
143        sender: ComponentSender<Self>,
144    ) -> ComponentParts<Self> {
145        let tasks =
146            FactoryVecDeque::builder()
147                .launch_default()
148                .forward(sender.input_sender(), |output| match output {
149                    TaskOutput::Delete(index) => AppMsg::DeleteEntry(index),
150                });
151
152        let model = App { tasks };
153
154        let task_list_box = model.tasks.widget();
155        let widgets = view_output!();
156
157        ComponentParts { model, widgets }
158    }
relm4/examples/tab_factory.rs (line 162)
157    fn init(
158        counter: Self::Init,
159        root: Self::Root,
160        sender: ComponentSender<Self>,
161    ) -> ComponentParts<Self> {
162        let counters = FactoryVecDeque::builder()
163            .launch(adw::TabView::default())
164            .forward(sender.input_sender(), |output| match output {
165                CounterOutput::SendFront(index) => AppMsg::SendFront(index),
166                CounterOutput::MoveUp(index) => AppMsg::MoveUp(index),
167                CounterOutput::MoveDown(index) => AppMsg::MoveDown(index),
168            });
169        let model = App {
170            created_widgets: counter,
171            counters,
172        };
173
174        let tab_view = model.counters.widget();
175        let widgets = view_output!();
176
177        ComponentParts { model, widgets }
178    }
relm4/examples/grid_factory.rs (line 190)
184    fn init(
185        counter: Self::Init,
186        root: Self::Root,
187        sender: ComponentSender<Self>,
188    ) -> ComponentParts<Self> {
189        let counters =
190            FactoryVecDeque::builder()
191                .launch_default()
192                .forward(sender.input_sender(), |msg| match msg {
193                    CounterOutput::SendFront(index) => AppMsg::SendFront(index),
194                    CounterOutput::MoveUp(index) => AppMsg::MoveUp(index),
195                    CounterOutput::MoveDown(index) => AppMsg::MoveDown(index),
196                });
197
198        let model = App {
199            created_widgets: counter,
200            counters,
201        };
202
203        let counter_grid = model.counters.widget();
204        let widgets = view_output!();
205
206        ComponentParts { model, widgets }
207    }
relm4/examples/factory.rs (line 165)
159    fn init(
160        counter: Self::Init,
161        root: Self::Root,
162        sender: ComponentSender<Self>,
163    ) -> ComponentParts<Self> {
164        let counters =
165            FactoryVecDeque::builder()
166                .launch_default()
167                .forward(sender.input_sender(), |msg| match msg {
168                    CounterOutput::SendFront(index) => AppMsg::SendFront(index),
169                    CounterOutput::MoveUp(index) => AppMsg::MoveUp(index),
170                    CounterOutput::MoveDown(index) => AppMsg::MoveDown(index),
171                    CounterOutput::Remove(index) => AppMsg::Remove(index),
172                });
173
174        let model = App {
175            created_widgets: counter,
176            counters,
177        };
178
179        let counter_box = model.counters.widget();
180        let widgets = view_output!();
181
182        ComponentParts { model, widgets }
183    }
Source

pub fn guard(&mut self) -> FactoryVecDequeGuard<'_, C>

Provides a FactoryVecDequeGuard that can be used to edit the factory.

The changes will be rendered on the widgets after the guard goes out of scope.

Examples found in repository?
relm4/examples/to_do.rs (line 132)
129    fn update(&mut self, msg: AppMsg, _sender: ComponentSender<Self>) {
130        match msg {
131            AppMsg::DeleteEntry(index) => {
132                self.tasks.guard().remove(index.current_index());
133            }
134            AppMsg::AddEntry(name) => {
135                self.tasks.guard().push_back(name);
136            }
137        }
138    }
More examples
Hide additional examples
relm4/examples/state_management.rs (line 139)
133    fn update(&mut self, message: Self::Input, _sender: FactorySender<Self>) {
134        match message {
135            TaskInput::ChangedName(name) => {
136                self.name = name;
137            }
138            TaskInput::AddedTag(name) => {
139                self.tags.guard().push_back(name);
140            }
141            TaskInput::DeletedTag(index) => {
142                self.tags.guard().remove(index);
143            }
144        }
145    }
146
147    fn init_widgets(
148        &mut self,
149        index: &Self::Index,
150        root: Self::Root,
151        _returned_widget: &<Self::ParentWidget as FactoryView>::ReturnedWidget,
152        sender: FactorySender<Self>,
153    ) -> Self::Widgets {
154        let tag_list_box = self.tags.widget();
155
156        let widgets = view_output!();
157
158        widgets
159    }
160
161    fn init_model(_name: Self::Init, index: &DynamicIndex, sender: FactorySender<Self>) -> Self {
162        let task_index = index.clone();
163
164        let tags = FactoryVecDeque::builder().launch_default().forward(
165            sender.output_sender(),
166            move |output| match output {
167                TagOutput::Delete(tag_index) => {
168                    TaskOutput::DeleteTag(task_index.clone(), tag_index)
169                }
170            },
171        );
172
173        Self {
174            name: "".into(),
175            tags,
176        }
177    }
178}
179
180#[derive(Debug)]
181struct Tag {
182    name: String,
183}
184
185#[derive(Debug)]
186enum TagInput {}
187
188#[derive(Debug)]
189enum TagOutput {
190    Delete(DynamicIndex),
191}
192
193#[relm4::factory]
194impl FactoryComponent for Tag {
195    type Init = String;
196    type Input = TagInput;
197    type Output = TagOutput;
198    type CommandOutput = ();
199    type ParentWidget = gtk::Box;
200
201    view! {
202        gtk::MenuButton {
203            #[watch]
204            set_label: &self.name,
205
206            #[wrap(Some)]
207            set_popover = &gtk::Popover {
208                gtk::Button {
209                    set_label: "Delete",
210
211                    connect_clicked[sender, index] => move |_| {
212                        sender.output(TagOutput::Delete(index.clone())).unwrap();
213                    }
214                }
215            }
216        }
217    }
218
219    fn init_model(name: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
220        Self { name }
221    }
222}
223
224/// The document is a headless component which holds and manages the data model.
225/// It receives input events FROM the App to update the data model.
226/// When updates to the model occur, it sends output events TO the App.
227///
228/// The document's interface is just input and output events. As a result you have a lot of freedom
229/// in how you choose to store the data model within the component, which backing store you use
230/// (such as the file system, a database, or a Web API), and how you synchronise to the backing
231/// store (e.g. manual save/load control, auto-saving on each change, batching up changes before
232/// syncing, and so on).
233struct Document {
234    /// The application data model.
235    /// In this case we have just stored the whole thing in memory because our requirements are
236    /// simple. In a real app you might choose a more elaborate approach.
237    model: Model,
238}
239
240#[derive(Default, Serialize, Deserialize)]
241struct TagModel {
242    name: String,
243}
244#[derive(Default, Serialize, Deserialize)]
245struct TaskModel {
246    name: String,
247    tags: Vec<TagModel>,
248}
249#[derive(Default, Serialize, Deserialize)]
250struct Model {
251    tasks: Vec<TaskModel>,
252}
253
254#[derive(Debug)]
255enum DocumentInput {
256    // extra operations on the document itself (in this case, related to file I/O)
257    Open(PathBuf),
258    Save(PathBuf),
259
260    // events related to the model that the document stores
261    Clear,
262    AddTask,
263    DeleteTask(DynamicIndex),
264    ChangeTaskName(DynamicIndex, String),
265    AddTag(DynamicIndex, String),
266    DeleteTag(DynamicIndex, DynamicIndex),
267}
268
269#[derive(Debug)]
270enum DocumentOutput {
271    Cleared,
272    AddedTask,
273    DeletedTask(usize),
274    ChangedTaskName(usize, String),
275    AddedTag(usize, String),
276    DeletedTag(usize, usize),
277}
278
279impl Worker for Document {
280    type Init = ();
281    type Input = DocumentInput;
282    type Output = DocumentOutput;
283
284    fn init(_init: Self::Init, _sender: ComponentSender<Self>) -> Self {
285        let model = Model::default();
286        Self { model }
287    }
288
289    fn update(&mut self, input: DocumentInput, sender: ComponentSender<Self>) {
290        match input {
291            DocumentInput::Save(path) => {
292                println!("Save as JSON to {path:?}");
293
294                // TODO in a real app you would report any errors from saving the document
295                if let Ok(json) = serde_json::to_string(&self.model) {
296                    std::fs::write(path, json).unwrap();
297                }
298            }
299            DocumentInput::Open(path) => {
300                println!("Open tasks document at {path:?}");
301
302                if let Ok(json) = std::fs::read_to_string(path)
303                    && let Ok(new_model) = serde_json::from_str(&json)
304                {
305                    // update the data model
306                    self.model = new_model;
307
308                    // refresh the view from the data model
309                    let _ = sender.output(DocumentOutput::Cleared);
310
311                    for (task_index, task) in self.model.tasks.iter().enumerate() {
312                        let _ = sender.output(DocumentOutput::AddedTask);
313
314                        let task_name = task.name.clone();
315                        let _ =
316                            sender.output(DocumentOutput::ChangedTaskName(task_index, task_name));
317
318                        for tag in &task.tags {
319                            let tag_name = tag.name.clone();
320                            let _ = sender.output(DocumentOutput::AddedTag(task_index, tag_name));
321                        }
322                    }
323                }
324            }
325            DocumentInput::Clear => {
326                self.model.tasks.clear();
327
328                let _ = sender.output(DocumentOutput::Cleared);
329            }
330            DocumentInput::AddTask => {
331                self.model.tasks.push(TaskModel::default());
332
333                let _ = sender.output(DocumentOutput::AddedTask);
334            }
335            DocumentInput::DeleteTask(index) => {
336                self.model.tasks.remove(index.current_index());
337
338                let _ = sender.output(DocumentOutput::DeletedTask(index.current_index()));
339            }
340            DocumentInput::ChangeTaskName(index, name) => {
341                if let Some(task) = self.model.tasks.get_mut(index.current_index()) {
342                    task.name.clone_from(&name);
343                }
344
345                // We don't technically need to send an event, because gtk::Entry updates itself
346                // this is just to make the example consistent.
347                let _ = sender.output(DocumentOutput::ChangedTaskName(index.current_index(), name));
348            }
349            DocumentInput::AddTag(task_index, name) => {
350                if let Some(task) = self.model.tasks.get_mut(task_index.current_index()) {
351                    task.tags.push(TagModel { name: name.clone() })
352                }
353
354                let _ = sender.output(DocumentOutput::AddedTag(task_index.current_index(), name));
355            }
356            DocumentInput::DeleteTag(task_index, tag_index) => {
357                if let Some(task) = self.model.tasks.get_mut(task_index.current_index()) {
358                    task.tags.remove(tag_index.current_index());
359                }
360
361                let _ = sender.output(DocumentOutput::DeletedTag(
362                    task_index.current_index(),
363                    tag_index.current_index(),
364                ));
365            }
366        }
367    }
368}
369
370/// The App is at the top level.
371/// It acts as a bridge between the view and the document, forwarding events between them.
372struct App {
373    view: FactoryVecDeque<Task>,
374    document: Controller<Document>,
375    save_dialog: Controller<SaveDialog>,
376    open_dialog: Controller<OpenDialog>,
377}
378
379#[derive(Debug)]
380enum AppInput {
381    Clear,
382    Cleared,
383
384    AddTask,
385    AddedTask,
386
387    DeleteTask(DynamicIndex),
388    DeletedTask(usize),
389
390    ChangeTaskName(DynamicIndex, String),
391    ChangedTaskName(usize, String),
392
393    AddTag(DynamicIndex, String),
394    AddedTag(usize, String),
395
396    DeleteTag(DynamicIndex, DynamicIndex),
397    DeletedTag(usize, usize),
398
399    // No-op event for when load/save dialogs result in Cancel
400    None,
401    Open,
402    OpenResponse(PathBuf),
403    Save,
404    SaveResponse(PathBuf),
405}
406
407#[relm4::component]
408impl SimpleComponent for App {
409    type Init = ();
410    type Input = AppInput;
411    type Output = ();
412
413    view! {
414        main_window = gtk::ApplicationWindow {
415            set_width_request: 360,
416            set_title: Some("Tasks"),
417
418            gtk::Box {
419                set_orientation: gtk::Orientation::Vertical,
420
421                gtk::HeaderBar {
422                    set_show_title_buttons: false,
423
424                    #[wrap(Some)]
425                    set_title_widget = &gtk::Label {
426                        set_text: ""
427                    },
428
429                    pack_start = &gtk::Button {
430                        set_icon_name: "plus",
431                        set_tooltip: "Add Task",
432
433                        connect_clicked[sender] => move |_| {
434                            sender.input(AppInput::AddTask);
435                        }
436                    },
437
438                    pack_end = &gtk::Button {
439                        set_label: "Save",
440                        connect_clicked => AppInput::Save,
441                    },
442                    pack_end = &gtk::Button {
443                        set_label: "Open",
444                        connect_clicked => AppInput::Open,
445                    },
446                },
447
448                gtk::ScrolledWindow {
449                    set_hscrollbar_policy: gtk::PolicyType::Never,
450                    set_min_content_height: 360,
451                    set_vexpand: true,
452
453                    #[local_ref]
454                    task_list_box -> gtk::ListBox {
455                        set_selection_mode: gtk::SelectionMode::None,
456                    }
457                },
458
459                gtk::Box {
460                    set_hexpand: true,
461                    set_spacing: DEFAULT_SPACING,
462                    set_orientation: gtk::Orientation::Horizontal,
463
464                    gtk::Label {
465                        set_text: "Press Enter after editing task names",
466                        set_hexpand: true,
467                        set_xalign: XALIGN_CENTER,
468                    },
469
470                    gtk::Button {
471                        set_icon_name: "edit-delete",
472                        set_tooltip: "Delete All Tasks",
473                        add_css_class: CSS_CLASS_DESTRUCTIVE_ACTION,
474
475                        connect_clicked[sender] => move |_| {
476                            sender.input(AppInput::Clear);
477                        }
478                    }
479                }
480            }
481        }
482    }
483
484    fn update(&mut self, msg: AppInput, _sender: ComponentSender<Self>) {
485        match msg {
486            AppInput::Clear => {
487                self.document.emit(DocumentInput::Clear);
488            }
489            AppInput::Cleared => {
490                self.view.guard().clear();
491            }
492            AppInput::AddTask => {
493                self.document.emit(DocumentInput::AddTask);
494            }
495            AppInput::AddedTask => {
496                self.view.guard().push_back(());
497            }
498            AppInput::DeleteTask(index) => {
499                self.document.emit(DocumentInput::DeleteTask(index));
500            }
501            AppInput::DeletedTask(index) => {
502                self.view.guard().remove(index);
503            }
504            AppInput::ChangeTaskName(index, name) => {
505                self.document
506                    .emit(DocumentInput::ChangeTaskName(index, name));
507            }
508            AppInput::ChangedTaskName(index, name) => {
509                self.view.guard().send(index, TaskInput::ChangedName(name));
510            }
511            AppInput::AddTag(index, name) => {
512                self.document.emit(DocumentInput::AddTag(index, name));
513            }
514            AppInput::AddedTag(index, name) => {
515                self.view.guard().send(index, TaskInput::AddedTag(name));
516            }
517            AppInput::DeleteTag(task_index, tag_index) => {
518                self.document
519                    .emit(DocumentInput::DeleteTag(task_index, tag_index));
520            }
521            AppInput::DeletedTag(task_index, tag_index) => {
522                self.view
523                    .guard()
524                    .send(task_index, TaskInput::DeletedTag(tag_index));
525            }
526            AppInput::None => {}
527            AppInput::Save => {
528                let name = "tasks.json".into();
529                self.save_dialog.emit(SaveDialogMsg::SaveAs(name));
530            }
531            AppInput::SaveResponse(path) => {
532                self.document.emit(DocumentInput::Save(path));
533            }
534            AppInput::Open => {
535                self.open_dialog.emit(OpenDialogMsg::Open);
536            }
537            AppInput::OpenResponse(path) => {
538                self.document.emit(DocumentInput::Open(path));
539            }
540        }
541    }
relm4/examples/tab_game.rs (line 233)
213    fn init(
214        _: Self::Init,
215        root: Self::Root,
216        sender: ComponentSender<Self>,
217    ) -> ComponentParts<Self> {
218        let counters = FactoryVecDeque::builder()
219            .launch(adw::TabView::default())
220            .forward(sender.input_sender(), |output| match output {
221                CounterOutput::StartGame(index) => AppMsg::StartGame(index),
222                CounterOutput::SelectedGuess(guess) => AppMsg::SelectedGuess(guess),
223            });
224
225        let mut model = App {
226            counters,
227            start_index: None,
228        };
229
230        let tab_view = model.counters.widget();
231        let widgets = view_output!();
232
233        let mut counters_guard = model.counters.guard();
234        for i in 0..3 {
235            counters_guard.push_back(i);
236        }
237
238        // Explicitly drop the guard,
239        // so that 'model' is no longer borrowed
240        // and can be moved inside ComponentParts
241        counters_guard.drop();
242
243        ComponentParts { model, widgets }
244    }
245
246    fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>, _root: &Self::Root) {
247        match msg {
248            AppMsg::StartGame(index) => {
249                self.start_index = Some(index);
250                sender.command(|sender, _| async move {
251                    for i in (1..4).rev() {
252                        *GAME_STATE.write() = GameState::Countdown(i);
253                        relm4::tokio::time::sleep(Duration::from_millis(1000)).await;
254                    }
255                    *GAME_STATE.write() = GameState::Running;
256                    for _ in 0..20 {
257                        relm4::tokio::time::sleep(Duration::from_millis(500)).await;
258                        sender.send(false).unwrap();
259                    }
260                    relm4::tokio::time::sleep(Duration::from_millis(1000)).await;
261                    sender.send(true).unwrap();
262                });
263            }
264            AppMsg::StopGame => {
265                *GAME_STATE.write() = GameState::Guessing;
266            }
267            AppMsg::SelectedGuess(index) => {
268                *GAME_STATE.write() = GameState::End(index == self.start_index.take().unwrap());
269            }
270        }
271    }
272
273    fn update_cmd(
274        &mut self,
275        msg: Self::CommandOutput,
276        sender: ComponentSender<Self>,
277        _root: &Self::Root,
278    ) {
279        if msg {
280            sender.input(AppMsg::StopGame);
281        } else {
282            let mut counters_guard = self.counters.guard();
283            match rand::random::<u8>() % 3 {
284                0 => {
285                    counters_guard.swap(1, 2);
286                }
287                1 => {
288                    counters_guard.swap(0, 1);
289                }
290                _ => {
291                    let widget = counters_guard.widget();
292                    if !widget.select_next_page() {
293                        widget.select_previous_page();
294                    }
295                }
296            }
297        }
298    }
relm4/examples/grid_factory.rs (line 210)
209    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
210        let mut counters_guard = self.counters.guard();
211
212        match msg {
213            AppMsg::AddCounter => {
214                counters_guard.push_back(self.created_widgets);
215                self.created_widgets = self.created_widgets.wrapping_add(1);
216            }
217            AppMsg::RemoveCounter => {
218                counters_guard.pop_back();
219            }
220            AppMsg::SendFront(index) => {
221                counters_guard.move_front(index.current_index());
222            }
223            AppMsg::MoveDown(index) => {
224                let index = index.current_index();
225                let new_index = index + 1;
226                // Already at the end?
227                if new_index < counters_guard.len() {
228                    counters_guard.move_to(index, new_index);
229                }
230            }
231            AppMsg::MoveUp(index) => {
232                let index = index.current_index();
233                // Already at the start?
234                if index != 0 {
235                    counters_guard.move_to(index, index - 1);
236                }
237            }
238        }
239    }
relm4/examples/tab_factory.rs (line 181)
180    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
181        let mut counters_guard = self.counters.guard();
182
183        match msg {
184            AppMsg::AddCounter => {
185                counters_guard.push_back(self.created_widgets);
186                self.created_widgets = self.created_widgets.wrapping_add(1);
187            }
188            AppMsg::RemoveCounter => {
189                counters_guard.pop_back();
190            }
191            AppMsg::SendFront(index) => {
192                counters_guard.move_front(index.current_index());
193            }
194            AppMsg::MoveDown(index) => {
195                let index = index.current_index();
196                let new_index = index + 1;
197                // Already at the end?
198                if new_index < counters_guard.len() {
199                    counters_guard.move_to(index, new_index);
200                }
201            }
202            AppMsg::MoveUp(index) => {
203                let index = index.current_index();
204                // Already at the start?
205                if index != 0 {
206                    counters_guard.move_to(index, index - 1);
207                }
208            }
209        }
210    }
relm4/examples/entry.rs (line 104)
99    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
100        match message {
101            AppMsg::AddCounters => {
102                let text = self.entry.text();
103                if let Ok(v) = text.parse::<i32>() {
104                    let mut guard = self.counters.guard();
105                    if v.is_positive() {
106                        // add as many counters as user entered
107                        for _ in 0..v {
108                            guard.push_back(self.created_counters);
109                            self.created_counters += 1;
110                        }
111                    } else if v.is_negative() {
112                        // remove counters
113                        for _ in v..0 {
114                            guard.pop_front();
115                        }
116                    }
117
118                    // clearing the entry value clears the entry widget
119                    self.entry.set_text("");
120                }
121            }
122            AppMsg::Clicked(index) => {
123                if let Some(counter) = self.counters.guard().get_mut(index.current_index()) {
124                    counter.value = counter.value.wrapping_sub(1);
125                }
126            }
127        }
128    }
Source

pub fn len(&self) -> usize

Returns the number of elements in the FactoryVecDeque.

Examples found in repository?
relm4/examples/grid_factory.rs (line 227)
209    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
210        let mut counters_guard = self.counters.guard();
211
212        match msg {
213            AppMsg::AddCounter => {
214                counters_guard.push_back(self.created_widgets);
215                self.created_widgets = self.created_widgets.wrapping_add(1);
216            }
217            AppMsg::RemoveCounter => {
218                counters_guard.pop_back();
219            }
220            AppMsg::SendFront(index) => {
221                counters_guard.move_front(index.current_index());
222            }
223            AppMsg::MoveDown(index) => {
224                let index = index.current_index();
225                let new_index = index + 1;
226                // Already at the end?
227                if new_index < counters_guard.len() {
228                    counters_guard.move_to(index, new_index);
229                }
230            }
231            AppMsg::MoveUp(index) => {
232                let index = index.current_index();
233                // Already at the start?
234                if index != 0 {
235                    counters_guard.move_to(index, index - 1);
236                }
237            }
238        }
239    }
More examples
Hide additional examples
relm4/examples/tab_factory.rs (line 198)
180    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
181        let mut counters_guard = self.counters.guard();
182
183        match msg {
184            AppMsg::AddCounter => {
185                counters_guard.push_back(self.created_widgets);
186                self.created_widgets = self.created_widgets.wrapping_add(1);
187            }
188            AppMsg::RemoveCounter => {
189                counters_guard.pop_back();
190            }
191            AppMsg::SendFront(index) => {
192                counters_guard.move_front(index.current_index());
193            }
194            AppMsg::MoveDown(index) => {
195                let index = index.current_index();
196                let new_index = index + 1;
197                // Already at the end?
198                if new_index < counters_guard.len() {
199                    counters_guard.move_to(index, new_index);
200                }
201            }
202            AppMsg::MoveUp(index) => {
203                let index = index.current_index();
204                // Already at the start?
205                if index != 0 {
206                    counters_guard.move_to(index, index - 1);
207                }
208            }
209        }
210    }
relm4/examples/factory.rs (line 202)
185    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
186        let mut counters_guard = self.counters.guard();
187        match msg {
188            AppMsg::AddCounter => {
189                counters_guard.push_back(self.created_widgets);
190                self.created_widgets = self.created_widgets.wrapping_add(1);
191            }
192            AppMsg::RemoveCounter => {
193                counters_guard.pop_back();
194            }
195            AppMsg::SendFront(index) => {
196                counters_guard.move_front(index.current_index());
197            }
198            AppMsg::MoveDown(index) => {
199                let index = index.current_index();
200                let new_index = index + 1;
201                // Already at the end?
202                if new_index < counters_guard.len() {
203                    counters_guard.move_to(index, new_index);
204                }
205            }
206            AppMsg::MoveUp(index) => {
207                let index = index.current_index();
208                // Already at the start?
209                if index != 0 {
210                    counters_guard.move_to(index, index - 1);
211                }
212            }
213            AppMsg::Remove(index) => {
214                counters_guard.remove(index.current_index());
215            }
216        }
217    }
Source

pub fn is_empty(&self) -> bool

Returns true if the FactoryVecDeque is empty.

Source

pub fn send(&self, index: usize, msg: C::Input)

Send a message to one of the elements.

Examples found in repository?
relm4/examples/state_management.rs (line 509)
484    fn update(&mut self, msg: AppInput, _sender: ComponentSender<Self>) {
485        match msg {
486            AppInput::Clear => {
487                self.document.emit(DocumentInput::Clear);
488            }
489            AppInput::Cleared => {
490                self.view.guard().clear();
491            }
492            AppInput::AddTask => {
493                self.document.emit(DocumentInput::AddTask);
494            }
495            AppInput::AddedTask => {
496                self.view.guard().push_back(());
497            }
498            AppInput::DeleteTask(index) => {
499                self.document.emit(DocumentInput::DeleteTask(index));
500            }
501            AppInput::DeletedTask(index) => {
502                self.view.guard().remove(index);
503            }
504            AppInput::ChangeTaskName(index, name) => {
505                self.document
506                    .emit(DocumentInput::ChangeTaskName(index, name));
507            }
508            AppInput::ChangedTaskName(index, name) => {
509                self.view.guard().send(index, TaskInput::ChangedName(name));
510            }
511            AppInput::AddTag(index, name) => {
512                self.document.emit(DocumentInput::AddTag(index, name));
513            }
514            AppInput::AddedTag(index, name) => {
515                self.view.guard().send(index, TaskInput::AddedTag(name));
516            }
517            AppInput::DeleteTag(task_index, tag_index) => {
518                self.document
519                    .emit(DocumentInput::DeleteTag(task_index, tag_index));
520            }
521            AppInput::DeletedTag(task_index, tag_index) => {
522                self.view
523                    .guard()
524                    .send(task_index, TaskInput::DeletedTag(tag_index));
525            }
526            AppInput::None => {}
527            AppInput::Save => {
528                let name = "tasks.json".into();
529                self.save_dialog.emit(SaveDialogMsg::SaveAs(name));
530            }
531            AppInput::SaveResponse(path) => {
532                self.document.emit(DocumentInput::Save(path));
533            }
534            AppInput::Open => {
535                self.open_dialog.emit(OpenDialogMsg::Open);
536            }
537            AppInput::OpenResponse(path) => {
538                self.document.emit(DocumentInput::Open(path));
539            }
540        }
541    }
Source

pub fn broadcast(&self, msg: C::Input)
where C::Input: Clone,

Send clone of a message to all of the elements.

Source

pub fn get(&self, index: usize) -> Option<&C>

Tries to get an immutable reference to the model of one element.

Returns None if index is invalid.

Source

pub fn back(&self) -> Option<&C>

Provides a reference to the model of the back element.

Returns None if the deque is empty.

Source

pub fn front(&self) -> Option<&C>

Provides a reference to the model of the front element.

Returns None if the deque is empty.

Source

pub const fn widget(&self) -> &C::ParentWidget

Returns the widget all components are attached to.

Examples found in repository?
relm4/examples/state_management.rs (line 154)
147    fn init_widgets(
148        &mut self,
149        index: &Self::Index,
150        root: Self::Root,
151        _returned_widget: &<Self::ParentWidget as FactoryView>::ReturnedWidget,
152        sender: FactorySender<Self>,
153    ) -> Self::Widgets {
154        let tag_list_box = self.tags.widget();
155
156        let widgets = view_output!();
157
158        widgets
159    }
160
161    fn init_model(_name: Self::Init, index: &DynamicIndex, sender: FactorySender<Self>) -> Self {
162        let task_index = index.clone();
163
164        let tags = FactoryVecDeque::builder().launch_default().forward(
165            sender.output_sender(),
166            move |output| match output {
167                TagOutput::Delete(tag_index) => {
168                    TaskOutput::DeleteTag(task_index.clone(), tag_index)
169                }
170            },
171        );
172
173        Self {
174            name: "".into(),
175            tags,
176        }
177    }
178}
179
180#[derive(Debug)]
181struct Tag {
182    name: String,
183}
184
185#[derive(Debug)]
186enum TagInput {}
187
188#[derive(Debug)]
189enum TagOutput {
190    Delete(DynamicIndex),
191}
192
193#[relm4::factory]
194impl FactoryComponent for Tag {
195    type Init = String;
196    type Input = TagInput;
197    type Output = TagOutput;
198    type CommandOutput = ();
199    type ParentWidget = gtk::Box;
200
201    view! {
202        gtk::MenuButton {
203            #[watch]
204            set_label: &self.name,
205
206            #[wrap(Some)]
207            set_popover = &gtk::Popover {
208                gtk::Button {
209                    set_label: "Delete",
210
211                    connect_clicked[sender, index] => move |_| {
212                        sender.output(TagOutput::Delete(index.clone())).unwrap();
213                    }
214                }
215            }
216        }
217    }
218
219    fn init_model(name: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
220        Self { name }
221    }
222}
223
224/// The document is a headless component which holds and manages the data model.
225/// It receives input events FROM the App to update the data model.
226/// When updates to the model occur, it sends output events TO the App.
227///
228/// The document's interface is just input and output events. As a result you have a lot of freedom
229/// in how you choose to store the data model within the component, which backing store you use
230/// (such as the file system, a database, or a Web API), and how you synchronise to the backing
231/// store (e.g. manual save/load control, auto-saving on each change, batching up changes before
232/// syncing, and so on).
233struct Document {
234    /// The application data model.
235    /// In this case we have just stored the whole thing in memory because our requirements are
236    /// simple. In a real app you might choose a more elaborate approach.
237    model: Model,
238}
239
240#[derive(Default, Serialize, Deserialize)]
241struct TagModel {
242    name: String,
243}
244#[derive(Default, Serialize, Deserialize)]
245struct TaskModel {
246    name: String,
247    tags: Vec<TagModel>,
248}
249#[derive(Default, Serialize, Deserialize)]
250struct Model {
251    tasks: Vec<TaskModel>,
252}
253
254#[derive(Debug)]
255enum DocumentInput {
256    // extra operations on the document itself (in this case, related to file I/O)
257    Open(PathBuf),
258    Save(PathBuf),
259
260    // events related to the model that the document stores
261    Clear,
262    AddTask,
263    DeleteTask(DynamicIndex),
264    ChangeTaskName(DynamicIndex, String),
265    AddTag(DynamicIndex, String),
266    DeleteTag(DynamicIndex, DynamicIndex),
267}
268
269#[derive(Debug)]
270enum DocumentOutput {
271    Cleared,
272    AddedTask,
273    DeletedTask(usize),
274    ChangedTaskName(usize, String),
275    AddedTag(usize, String),
276    DeletedTag(usize, usize),
277}
278
279impl Worker for Document {
280    type Init = ();
281    type Input = DocumentInput;
282    type Output = DocumentOutput;
283
284    fn init(_init: Self::Init, _sender: ComponentSender<Self>) -> Self {
285        let model = Model::default();
286        Self { model }
287    }
288
289    fn update(&mut self, input: DocumentInput, sender: ComponentSender<Self>) {
290        match input {
291            DocumentInput::Save(path) => {
292                println!("Save as JSON to {path:?}");
293
294                // TODO in a real app you would report any errors from saving the document
295                if let Ok(json) = serde_json::to_string(&self.model) {
296                    std::fs::write(path, json).unwrap();
297                }
298            }
299            DocumentInput::Open(path) => {
300                println!("Open tasks document at {path:?}");
301
302                if let Ok(json) = std::fs::read_to_string(path)
303                    && let Ok(new_model) = serde_json::from_str(&json)
304                {
305                    // update the data model
306                    self.model = new_model;
307
308                    // refresh the view from the data model
309                    let _ = sender.output(DocumentOutput::Cleared);
310
311                    for (task_index, task) in self.model.tasks.iter().enumerate() {
312                        let _ = sender.output(DocumentOutput::AddedTask);
313
314                        let task_name = task.name.clone();
315                        let _ =
316                            sender.output(DocumentOutput::ChangedTaskName(task_index, task_name));
317
318                        for tag in &task.tags {
319                            let tag_name = tag.name.clone();
320                            let _ = sender.output(DocumentOutput::AddedTag(task_index, tag_name));
321                        }
322                    }
323                }
324            }
325            DocumentInput::Clear => {
326                self.model.tasks.clear();
327
328                let _ = sender.output(DocumentOutput::Cleared);
329            }
330            DocumentInput::AddTask => {
331                self.model.tasks.push(TaskModel::default());
332
333                let _ = sender.output(DocumentOutput::AddedTask);
334            }
335            DocumentInput::DeleteTask(index) => {
336                self.model.tasks.remove(index.current_index());
337
338                let _ = sender.output(DocumentOutput::DeletedTask(index.current_index()));
339            }
340            DocumentInput::ChangeTaskName(index, name) => {
341                if let Some(task) = self.model.tasks.get_mut(index.current_index()) {
342                    task.name.clone_from(&name);
343                }
344
345                // We don't technically need to send an event, because gtk::Entry updates itself
346                // this is just to make the example consistent.
347                let _ = sender.output(DocumentOutput::ChangedTaskName(index.current_index(), name));
348            }
349            DocumentInput::AddTag(task_index, name) => {
350                if let Some(task) = self.model.tasks.get_mut(task_index.current_index()) {
351                    task.tags.push(TagModel { name: name.clone() })
352                }
353
354                let _ = sender.output(DocumentOutput::AddedTag(task_index.current_index(), name));
355            }
356            DocumentInput::DeleteTag(task_index, tag_index) => {
357                if let Some(task) = self.model.tasks.get_mut(task_index.current_index()) {
358                    task.tags.remove(tag_index.current_index());
359                }
360
361                let _ = sender.output(DocumentOutput::DeletedTag(
362                    task_index.current_index(),
363                    tag_index.current_index(),
364                ));
365            }
366        }
367    }
368}
369
370/// The App is at the top level.
371/// It acts as a bridge between the view and the document, forwarding events between them.
372struct App {
373    view: FactoryVecDeque<Task>,
374    document: Controller<Document>,
375    save_dialog: Controller<SaveDialog>,
376    open_dialog: Controller<OpenDialog>,
377}
378
379#[derive(Debug)]
380enum AppInput {
381    Clear,
382    Cleared,
383
384    AddTask,
385    AddedTask,
386
387    DeleteTask(DynamicIndex),
388    DeletedTask(usize),
389
390    ChangeTaskName(DynamicIndex, String),
391    ChangedTaskName(usize, String),
392
393    AddTag(DynamicIndex, String),
394    AddedTag(usize, String),
395
396    DeleteTag(DynamicIndex, DynamicIndex),
397    DeletedTag(usize, usize),
398
399    // No-op event for when load/save dialogs result in Cancel
400    None,
401    Open,
402    OpenResponse(PathBuf),
403    Save,
404    SaveResponse(PathBuf),
405}
406
407#[relm4::component]
408impl SimpleComponent for App {
409    type Init = ();
410    type Input = AppInput;
411    type Output = ();
412
413    view! {
414        main_window = gtk::ApplicationWindow {
415            set_width_request: 360,
416            set_title: Some("Tasks"),
417
418            gtk::Box {
419                set_orientation: gtk::Orientation::Vertical,
420
421                gtk::HeaderBar {
422                    set_show_title_buttons: false,
423
424                    #[wrap(Some)]
425                    set_title_widget = &gtk::Label {
426                        set_text: ""
427                    },
428
429                    pack_start = &gtk::Button {
430                        set_icon_name: "plus",
431                        set_tooltip: "Add Task",
432
433                        connect_clicked[sender] => move |_| {
434                            sender.input(AppInput::AddTask);
435                        }
436                    },
437
438                    pack_end = &gtk::Button {
439                        set_label: "Save",
440                        connect_clicked => AppInput::Save,
441                    },
442                    pack_end = &gtk::Button {
443                        set_label: "Open",
444                        connect_clicked => AppInput::Open,
445                    },
446                },
447
448                gtk::ScrolledWindow {
449                    set_hscrollbar_policy: gtk::PolicyType::Never,
450                    set_min_content_height: 360,
451                    set_vexpand: true,
452
453                    #[local_ref]
454                    task_list_box -> gtk::ListBox {
455                        set_selection_mode: gtk::SelectionMode::None,
456                    }
457                },
458
459                gtk::Box {
460                    set_hexpand: true,
461                    set_spacing: DEFAULT_SPACING,
462                    set_orientation: gtk::Orientation::Horizontal,
463
464                    gtk::Label {
465                        set_text: "Press Enter after editing task names",
466                        set_hexpand: true,
467                        set_xalign: XALIGN_CENTER,
468                    },
469
470                    gtk::Button {
471                        set_icon_name: "edit-delete",
472                        set_tooltip: "Delete All Tasks",
473                        add_css_class: CSS_CLASS_DESTRUCTIVE_ACTION,
474
475                        connect_clicked[sender] => move |_| {
476                            sender.input(AppInput::Clear);
477                        }
478                    }
479                }
480            }
481        }
482    }
483
484    fn update(&mut self, msg: AppInput, _sender: ComponentSender<Self>) {
485        match msg {
486            AppInput::Clear => {
487                self.document.emit(DocumentInput::Clear);
488            }
489            AppInput::Cleared => {
490                self.view.guard().clear();
491            }
492            AppInput::AddTask => {
493                self.document.emit(DocumentInput::AddTask);
494            }
495            AppInput::AddedTask => {
496                self.view.guard().push_back(());
497            }
498            AppInput::DeleteTask(index) => {
499                self.document.emit(DocumentInput::DeleteTask(index));
500            }
501            AppInput::DeletedTask(index) => {
502                self.view.guard().remove(index);
503            }
504            AppInput::ChangeTaskName(index, name) => {
505                self.document
506                    .emit(DocumentInput::ChangeTaskName(index, name));
507            }
508            AppInput::ChangedTaskName(index, name) => {
509                self.view.guard().send(index, TaskInput::ChangedName(name));
510            }
511            AppInput::AddTag(index, name) => {
512                self.document.emit(DocumentInput::AddTag(index, name));
513            }
514            AppInput::AddedTag(index, name) => {
515                self.view.guard().send(index, TaskInput::AddedTag(name));
516            }
517            AppInput::DeleteTag(task_index, tag_index) => {
518                self.document
519                    .emit(DocumentInput::DeleteTag(task_index, tag_index));
520            }
521            AppInput::DeletedTag(task_index, tag_index) => {
522                self.view
523                    .guard()
524                    .send(task_index, TaskInput::DeletedTag(tag_index));
525            }
526            AppInput::None => {}
527            AppInput::Save => {
528                let name = "tasks.json".into();
529                self.save_dialog.emit(SaveDialogMsg::SaveAs(name));
530            }
531            AppInput::SaveResponse(path) => {
532                self.document.emit(DocumentInput::Save(path));
533            }
534            AppInput::Open => {
535                self.open_dialog.emit(OpenDialogMsg::Open);
536            }
537            AppInput::OpenResponse(path) => {
538                self.document.emit(DocumentInput::Open(path));
539            }
540        }
541    }
542
543    fn init(
544        _: Self::Init,
545        root: Self::Root,
546        sender: ComponentSender<Self>,
547    ) -> ComponentParts<Self> {
548        let view =
549            FactoryVecDeque::builder()
550                .launch_default()
551                .forward(sender.input_sender(), |msg| match msg {
552                    TaskOutput::Delete(index) => AppInput::DeleteTask(index),
553                    TaskOutput::Name(index, name) => AppInput::ChangeTaskName(index, name),
554                    TaskOutput::AddTag(index, name) => AppInput::AddTag(index, name),
555                    TaskOutput::DeleteTag(task_index, tag_index) => {
556                        AppInput::DeleteTag(task_index, tag_index)
557                    }
558                });
559
560        let document =
561            Document::builder()
562                .launch(())
563                .forward(sender.input_sender(), |msg| match msg {
564                    DocumentOutput::Cleared => AppInput::Cleared,
565                    DocumentOutput::DeletedTask(index) => AppInput::DeletedTask(index),
566                    DocumentOutput::DeletedTag(task_index, tag_index) => {
567                        AppInput::DeletedTag(task_index, tag_index)
568                    }
569                    DocumentOutput::AddedTask => AppInput::AddedTask,
570                    DocumentOutput::AddedTag(index, name) => AppInput::AddedTag(index, name),
571                    DocumentOutput::ChangedTaskName(index, name) => {
572                        AppInput::ChangedTaskName(index, name)
573                    }
574                });
575
576        let save_dialog = SaveDialog::builder()
577            .transient_for_native(&root)
578            .launch(SaveDialogSettings {
579                create_folders: true,
580                accept_label: "Save".into(),
581                cancel_label: "Cancel".into(),
582                is_modal: true,
583                filters: tasks_filename_filters(),
584            })
585            .forward(sender.input_sender(), |response| match response {
586                SaveDialogResponse::Accept(path) => AppInput::SaveResponse(path),
587                SaveDialogResponse::Cancel => AppInput::None,
588            });
589
590        let open_dialog = OpenDialog::builder()
591            .transient_for_native(&root)
592            .launch(OpenDialogSettings {
593                create_folders: false,
594                folder_mode: false,
595                cancel_label: "Cancel".into(),
596                accept_label: "Open".into(),
597                is_modal: true,
598                filters: tasks_filename_filters(),
599            })
600            .forward(sender.input_sender(), |response| match response {
601                OpenDialogResponse::Accept(path) => AppInput::OpenResponse(path),
602                OpenDialogResponse::Cancel => AppInput::None,
603            });
604
605        let app = App {
606            view,
607            document,
608            open_dialog,
609            save_dialog,
610        };
611
612        let task_list_box = app.view.widget();
613        let widgets = view_output!();
614
615        ComponentParts {
616            model: app,
617            widgets,
618        }
619    }
More examples
Hide additional examples
relm4/examples/to_do.rs (line 154)
140    fn init(
141        _: Self::Init,
142        root: Self::Root,
143        sender: ComponentSender<Self>,
144    ) -> ComponentParts<Self> {
145        let tasks =
146            FactoryVecDeque::builder()
147                .launch_default()
148                .forward(sender.input_sender(), |output| match output {
149                    TaskOutput::Delete(index) => AppMsg::DeleteEntry(index),
150                });
151
152        let model = App { tasks };
153
154        let task_list_box = model.tasks.widget();
155        let widgets = view_output!();
156
157        ComponentParts { model, widgets }
158    }
relm4/examples/tab_factory.rs (line 174)
157    fn init(
158        counter: Self::Init,
159        root: Self::Root,
160        sender: ComponentSender<Self>,
161    ) -> ComponentParts<Self> {
162        let counters = FactoryVecDeque::builder()
163            .launch(adw::TabView::default())
164            .forward(sender.input_sender(), |output| match output {
165                CounterOutput::SendFront(index) => AppMsg::SendFront(index),
166                CounterOutput::MoveUp(index) => AppMsg::MoveUp(index),
167                CounterOutput::MoveDown(index) => AppMsg::MoveDown(index),
168            });
169        let model = App {
170            created_widgets: counter,
171            counters,
172        };
173
174        let tab_view = model.counters.widget();
175        let widgets = view_output!();
176
177        ComponentParts { model, widgets }
178    }
relm4/examples/grid_factory.rs (line 203)
184    fn init(
185        counter: Self::Init,
186        root: Self::Root,
187        sender: ComponentSender<Self>,
188    ) -> ComponentParts<Self> {
189        let counters =
190            FactoryVecDeque::builder()
191                .launch_default()
192                .forward(sender.input_sender(), |msg| match msg {
193                    CounterOutput::SendFront(index) => AppMsg::SendFront(index),
194                    CounterOutput::MoveUp(index) => AppMsg::MoveUp(index),
195                    CounterOutput::MoveDown(index) => AppMsg::MoveDown(index),
196                });
197
198        let model = App {
199            created_widgets: counter,
200            counters,
201        };
202
203        let counter_grid = model.counters.widget();
204        let widgets = view_output!();
205
206        ComponentParts { model, widgets }
207    }
relm4/examples/factory.rs (line 179)
159    fn init(
160        counter: Self::Init,
161        root: Self::Root,
162        sender: ComponentSender<Self>,
163    ) -> ComponentParts<Self> {
164        let counters =
165            FactoryVecDeque::builder()
166                .launch_default()
167                .forward(sender.input_sender(), |msg| match msg {
168                    CounterOutput::SendFront(index) => AppMsg::SendFront(index),
169                    CounterOutput::MoveUp(index) => AppMsg::MoveUp(index),
170                    CounterOutput::MoveDown(index) => AppMsg::MoveDown(index),
171                    CounterOutput::Remove(index) => AppMsg::Remove(index),
172                });
173
174        let model = App {
175            created_widgets: counter,
176            counters,
177        };
178
179        let counter_box = model.counters.widget();
180        let widgets = view_output!();
181
182        ComponentParts { model, widgets }
183    }
relm4/examples/tab_game.rs (line 230)
213    fn init(
214        _: Self::Init,
215        root: Self::Root,
216        sender: ComponentSender<Self>,
217    ) -> ComponentParts<Self> {
218        let counters = FactoryVecDeque::builder()
219            .launch(adw::TabView::default())
220            .forward(sender.input_sender(), |output| match output {
221                CounterOutput::StartGame(index) => AppMsg::StartGame(index),
222                CounterOutput::SelectedGuess(guess) => AppMsg::SelectedGuess(guess),
223            });
224
225        let mut model = App {
226            counters,
227            start_index: None,
228        };
229
230        let tab_view = model.counters.widget();
231        let widgets = view_output!();
232
233        let mut counters_guard = model.counters.guard();
234        for i in 0..3 {
235            counters_guard.push_back(i);
236        }
237
238        // Explicitly drop the guard,
239        // so that 'model' is no longer borrowed
240        // and can be moved inside ComponentParts
241        counters_guard.drop();
242
243        ComponentParts { model, widgets }
244    }
245
246    fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>, _root: &Self::Root) {
247        match msg {
248            AppMsg::StartGame(index) => {
249                self.start_index = Some(index);
250                sender.command(|sender, _| async move {
251                    for i in (1..4).rev() {
252                        *GAME_STATE.write() = GameState::Countdown(i);
253                        relm4::tokio::time::sleep(Duration::from_millis(1000)).await;
254                    }
255                    *GAME_STATE.write() = GameState::Running;
256                    for _ in 0..20 {
257                        relm4::tokio::time::sleep(Duration::from_millis(500)).await;
258                        sender.send(false).unwrap();
259                    }
260                    relm4::tokio::time::sleep(Duration::from_millis(1000)).await;
261                    sender.send(true).unwrap();
262                });
263            }
264            AppMsg::StopGame => {
265                *GAME_STATE.write() = GameState::Guessing;
266            }
267            AppMsg::SelectedGuess(index) => {
268                *GAME_STATE.write() = GameState::End(index == self.start_index.take().unwrap());
269            }
270        }
271    }
272
273    fn update_cmd(
274        &mut self,
275        msg: Self::CommandOutput,
276        sender: ComponentSender<Self>,
277        _root: &Self::Root,
278    ) {
279        if msg {
280            sender.input(AppMsg::StopGame);
281        } else {
282            let mut counters_guard = self.counters.guard();
283            match rand::random::<u8>() % 3 {
284                0 => {
285                    counters_guard.swap(1, 2);
286                }
287                1 => {
288                    counters_guard.swap(0, 1);
289                }
290                _ => {
291                    let widget = counters_guard.widget();
292                    if !widget.select_next_page() {
293                        widget.select_previous_page();
294                    }
295                }
296            }
297        }
298    }
Source

pub fn iter( &self, ) -> impl DoubleEndedIterator<Item = &C> + ExactSizeIterator + FusedIterator

Returns an iterator over the components.

Source

pub fn from_iter( component_iter: impl IntoIterator<Item = C::Init>, widget: C::ParentWidget, ) -> Self

Creates a FactoryVecDeque from any IntoIterator

Source

pub fn extend(&mut self, component_iter: impl IntoIterator<Item = C::Init>)

Adds multiple components to the back of the FactoryVecDeque.

This method takes an iterator over components and adds each one to the deque, rendering them efficiently.

Source§

impl<C> FactoryVecDeque<C>

Source

pub fn set_visible(&self, index: usize) -> bool

Makes the element at a given index visible in a gtk::Stack. Returns true on success, otherwise false.

Trait Implementations§

Source§

impl<C> Clone for FactoryVecDeque<C>

Implements the Clone Trait for FactoryVecDeque<C> where C is Cloneable

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<C> Debug for FactoryVecDeque<C>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<C> Drop for FactoryVecDeque<C>
where C: FactoryComponent<Index = DynamicIndex>,

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl<C> Index<usize> for FactoryVecDeque<C>
where C: FactoryComponent<Index = DynamicIndex>,

Source§

type Output = C

The returned type after indexing.
Source§

fn index(&self, index: usize) -> &Self::Output

Performs the indexing (container[index]) operation. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<C> AsyncPosition<()> for C

Source§

fn position(_index: usize)

Returns the position. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<C, I> Position<(), I> for C

Source§

fn position(&self, _index: &I)

Returns the position. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more