Skip to main content

truce_iced/widgets/
selector.rs

1//! Selector widget wrapping iced's `pick_list` for enum parameters.
2
3use std::fmt::Debug;
4use std::marker::PhantomData;
5
6use iced::Element;
7use iced::widget::{column, pick_list, text};
8
9use crate::param_cache::ParamCache;
10use crate::param_message::{Message, ParamMessage};
11use crate::theme;
12use truce_params::Params;
13
14/// Builder for a parameter-bound selector (pick list).
15pub struct SelectorWidget<'a, M> {
16    id: u32,
17    options: Vec<String>,
18    selected: Option<String>,
19    label: Option<&'a str>,
20    _phantom: PhantomData<M>,
21}
22
23impl<'a, M: Clone + Debug + 'static> SelectorWidget<'a, M> {
24    pub fn new(id: impl Into<u32>, params: &'a ParamCache<impl Params>) -> Self {
25        let id = id.into();
26        let value = params.get(id);
27        let infos = params.params().param_infos();
28        let info = infos.iter().find(|i| i.id == id);
29
30        let (options, selected) = if let Some(info) = info {
31            let count = info.range.step_count_usize();
32            let opts: Vec<String> = (0..count)
33                .map(|i| {
34                    let norm = truce_core::cast::discrete_norm(i, count);
35                    let plain = info.range.denormalize(norm);
36                    params
37                        .params()
38                        .format_value(id, plain)
39                        .unwrap_or_else(|| format!("{plain:.0}"))
40                })
41                .collect();
42            let sel_idx = truce_core::cast::discrete_index(value, count);
43            let selected = opts.get(sel_idx).cloned();
44            (opts, selected)
45        } else {
46            (vec![], None)
47        };
48
49        Self {
50            id,
51            options,
52            selected,
53            label: None,
54            _phantom: PhantomData,
55        }
56    }
57
58    #[must_use]
59    pub fn label(mut self, label: &'a str) -> Self {
60        self.label = Some(label);
61        self
62    }
63
64    #[must_use]
65    pub fn into_element(self) -> Element<'a, Message<M>> {
66        let id = self.id;
67        let count = self.options.len();
68        let options = self.options.clone();
69
70        let pl = pick_list(self.options, self.selected, move |selected: String| {
71            let idx = options.iter().position(|o| *o == selected).unwrap_or(0);
72            let norm = truce_core::cast::discrete_norm(idx, count);
73            Message::Param(ParamMessage::SetNormalized(id, norm))
74        });
75
76        let mut col = column![pl].spacing(4).align_x(iced::Alignment::Center);
77
78        if let Some(label) = self.label {
79            col = col.push(text(label).size(10).color(theme::TEXT_DIM));
80        }
81
82        col.into()
83    }
84}
85
86impl<'a, M: Clone + Debug + 'static> From<SelectorWidget<'a, M>> for Element<'a, Message<M>> {
87    fn from(s: SelectorWidget<'a, M>) -> Self {
88        s.into_element()
89    }
90}