FactoryVecDequeBuilder

Struct FactoryVecDequeBuilder 

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

A builder-pattern struct for building a FactoryVecDeque.

Implementations§

Source§

impl<C> FactoryVecDequeBuilder<C>

Source

pub fn launch_default(self) -> FactoryVecDequeConnector<C>

Launch the factory with a default parent widget.

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/to_do.rs (line 147)
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/grid_factory.rs (line 191)
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 166)
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§

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

Source

pub fn new() -> Self

Create a builder for this component.

Source

pub fn launch(self, widget: C::ParentWidget) -> FactoryVecDequeConnector<C>

Launch the factory. This is similar to Connector::launch.

Examples found in repository?
relm4/examples/entry.rs (line 85)
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    }
More examples
Hide additional examples
relm4/examples/tab_factory.rs (line 163)
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/tab_game.rs (line 219)
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    }

Trait Implementations§

Source§

impl<C> Debug for FactoryVecDequeBuilder<C>
where C: FactoryComponent<Index = DynamicIndex> + Debug,

Source§

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

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

impl<C> Default for FactoryVecDequeBuilder<C>
where C: FactoryComponent<Index = DynamicIndex>,

Source§

fn default() -> Self

Returns the “default value” for a type. 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> 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, 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