pub struct FactoryVecDequeConnector<C>where
C: FactoryComponent<Index = DynamicIndex>,{ /* private fields */ }Expand description
Second stage of the builder-pattern for building a FactoryVecDeque.
Implementations§
Source§impl<C> FactoryVecDequeConnector<C>where
C: FactoryComponent<Index = DynamicIndex>,
impl<C> FactoryVecDequeConnector<C>where
C: FactoryComponent<Index = DynamicIndex>,
Sourcepub fn forward<F, Msg>(self, sender_: &Sender<Msg>, f: F) -> FactoryVecDeque<C>
pub fn forward<F, Msg>(self, sender_: &Sender<Msg>, f: F) -> FactoryVecDeque<C>
Forwards output events from child components to the designated sender.
Examples found in repository?
relm4/examples/state_management.rs (lines 164-171)
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 = >k::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 = >k::Label {
426 set_text: ""
427 },
428
429 pack_start = >k::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 = >k::Button {
439 set_label: "Save",
440 connect_clicked => AppInput::Save,
441 },
442 pack_end = >k::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
relm4/examples/entry.rs (line 86)
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 (lines 148-150)
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 (lines 164-168)
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 (lines 192-196)
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 (lines 167-172)
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 }Additional examples can be found in:
Sourcepub fn detach(self) -> FactoryVecDeque<C>
pub fn detach(self) -> FactoryVecDeque<C>
Ignore output events from child components and just create the FactoryVecDeque.
Trait Implementations§
Source§impl<C> Debug for FactoryVecDequeConnector<C>
impl<C> Debug for FactoryVecDequeConnector<C>
Auto Trait Implementations§
impl<C> Freeze for FactoryVecDequeConnector<C>
impl<C> RefUnwindSafe for FactoryVecDequeConnector<C>
impl<C> Send for FactoryVecDequeConnector<C>
impl<C> Sync for FactoryVecDequeConnector<C>
impl<C> Unpin for FactoryVecDequeConnector<C>
impl<C> UnwindSafe for FactoryVecDequeConnector<C>
Blanket Implementations§
Source§impl<C> AsyncPosition<()> for C
impl<C> AsyncPosition<()> for C
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more