truce_iced/widgets/
selector.rs1use 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
14pub 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}