relm4_components/
simple_adw_combo_row.rs

1//! A wrapper around [`adw::ComboRow`] that makes it easier to use
2//! from regular Rust code.
3
4use std::fmt::Debug;
5
6use relm4::{Component, ComponentParts, ComponentSender, adw};
7
8use adw::gtk::StringList;
9use adw::prelude::ComboRowExt;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12/// A simple wrapper around [`adw::ComboRow`].
13///
14/// This can be used with enums, [`String`]s or any custom type you want.
15/// The only requirement is that the inner type implements [`ToString`] and [`Debug`].
16///
17/// To get notified when the selection changed, you can use
18/// [`Connector::forward()`](relm4::component::Connector::forward())
19/// after launching the component.
20pub struct SimpleComboRow<E: ToString> {
21    /// The variants that can be selected.
22    pub variants: Vec<E>,
23    /// The index of the active element or [`None`] is nothing is selected.
24    pub active_index: Option<usize>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28/// The message type of [`SimpleComboRow`].
29pub enum SimpleComboRowMsg<E: ToString> {
30    /// Overwrite the current values.
31    UpdateData(SimpleComboRow<E>),
32    /// Set the index of the active element.
33    SetActiveIdx(usize),
34    #[doc(hidden)]
35    UpdateIndex(usize),
36}
37
38impl<E> Component for SimpleComboRow<E>
39where
40    E: ToString + 'static + Debug,
41{
42    type CommandOutput = ();
43    type Input = SimpleComboRowMsg<E>;
44    type Output = usize;
45    type Init = Self;
46    type Root = adw::ComboRow;
47    type Widgets = adw::ComboRow;
48
49    fn init_root() -> Self::Root {
50        adw::ComboRow::default()
51    }
52
53    fn init(
54        model: Self::Init,
55        widgets: Self::Root,
56        sender: ComponentSender<Self>,
57    ) -> ComponentParts<Self> {
58        model.render(&widgets);
59
60        widgets.connect_selected_notify(move |combo_box| {
61            sender.input(Self::Input::UpdateIndex(combo_box.selected() as _));
62        });
63
64        ComponentParts { model, widgets }
65    }
66
67    fn update_with_view(
68        &mut self,
69        widgets: &mut Self::Widgets,
70        input: Self::Input,
71        sender: ComponentSender<Self>,
72        _root: &Self::Root,
73    ) {
74        match input {
75            SimpleComboRowMsg::UpdateIndex(idx) => {
76                // Ignore send errors because the component might
77                // be detached.
78                sender.output(idx).ok();
79                self.active_index = Some(idx);
80            }
81            SimpleComboRowMsg::SetActiveIdx(idx) => {
82                if idx < self.variants.len() {
83                    self.active_index = Some(idx);
84                    widgets.set_selected(idx as u32);
85                }
86            }
87            SimpleComboRowMsg::UpdateData(data) => {
88                *self = data;
89                self.render(widgets);
90            }
91        }
92    }
93}
94
95impl<E> SimpleComboRow<E>
96where
97    E: ToString,
98{
99    fn render(&self, combo_box: &adw::ComboRow) {
100        let model: StringList = self.variants.iter().map(ToString::to_string).collect();
101        combo_box.set_model(Some(&model));
102
103        if let Some(idx) = self.active_index {
104            combo_box.set_selected(idx as u32);
105        }
106    }
107
108    /// Return the value of the currently selected element or [`None`] if nothing is selected.
109    #[must_use]
110    pub fn get_active_elem(&self) -> Option<&E> {
111        self.active_index.map(|idx| &self.variants[idx])
112    }
113}