Skip to main content

feather_tui/components/
selector.rs

1use crate::{components as cpn, error::{FtuiResult, FtuiError}, trigger::Trigger};
2
3/// A UI component use for navigating and selecting `Option` components.
4/// It allows movement up and down between options and selection of an option.
5///
6/// # Usage
7/// A `Selector` is required for `Option` components in a `Container` to work.
8///
9/// # Notes
10/// - Without a `Selector`, `Option` components in a `Container` cannot be 
11/// selected or navigated.
12pub struct Selector {
13    up_trig: Option<Trigger>,
14    down_trig: Option<Trigger>,
15    selc_trig: Option<Trigger>,
16    on: usize,
17}
18
19impl Selector {
20    /// Constructs a new `Selector` component with triggers functions for 
21    /// navigating up, down, and selecting. Each action is associated with a `Trigger`,
22    /// which determines whether the corresponding input (up, down, or select) is activated.
23    ///
24    /// # Example
25    /// ```rust
26    /// // A `Trigger` that always activates.
27    /// tui::trg_new_trigger_func!(always_true_trigger, _arg, {
28    ///     Ok(true)
29    /// });
30    ///
31    /// // A `Trigger` that never activates.
32    /// tui::trg_new_trigger_func!(always_false_trigger, _arg, {
33    ///     Ok(false)
34    /// });
35    ///
36    /// // Create a `Selector` that always moves down when evaluated.
37    /// let selector = Selector::new(
38    ///     Trigger::no_arg(always_false_trigger), // up
39    ///     Trigger::no_arg(always_true_trigger),  // down
40    ///     Trigger::no_arg(always_false_trigger), // select
41    /// );
42    /// ```
43    pub fn new(up_trig: Trigger, down_trig: Trigger, selc_trig: Trigger) -> Self {
44        Selector {
45            up_trig: Some(up_trig),
46            down_trig: Some(down_trig),
47            selc_trig: Some(selc_trig),
48            on: 0,
49        }
50    }
51
52    /// Creates a new `Selector` component without any trigger functions.
53    /// When using this variant, you must manually control its behavior by calling
54    /// the `selector_up`, `selector_down`, and `selector_select` methods on the
55    /// `Container` that contains it.
56    ///
57    /// # Example
58    /// ```rust
59    /// // Create a new `Selector` with no triggers.
60    /// let _ = Selector::no_triggers();
61    ///
62    /// // Controlling the `Selector` manually.
63    ///
64    /// // Build a container that contains a manual `Selector`.
65    /// let mut container = ContainerBuilder::new()
66    ///     .selector_no_triggers()
67    ///     .build();
68    ///
69    /// // Move the selector up.
70    /// container.selector_up()?;
71    ///
72    /// // Move the selector down.
73    /// container.selector_down()?;
74    ///
75    /// // Select the currently highlighted option.
76    /// container.selector_select()?;
77    /// ```
78    pub fn no_triggers() -> Self {
79        Selector {
80            up_trig: None,
81            down_trig: None,
82            selc_trig: None,
83            on: 0,
84        }
85    }
86
87    pub(crate) fn up(&mut self, options: &mut Vec<cpn::Option>) -> bool {
88        if self.on == 0 {
89            return false;
90        }
91
92        // move the selector up
93        options[self.on].set_selc_on(false);
94        self.on -= 1;
95        options[self.on].set_selc_on(true);
96
97        true
98    }
99
100    pub(crate) fn down(&mut self, options: &mut Vec<cpn::Option>) -> bool {
101        if self.on == options.len() - 1 {
102            return false;
103        }
104
105        // move selector down
106        options[self.on].set_selc_on(false);
107        self.on += 1;
108        options[self.on].set_selc_on(true);
109
110        true
111    }
112
113    /// Select action always trigger an update.
114    pub(crate) fn select(&mut self, options: &mut Vec<cpn::Option>) -> FtuiResult<bool> {
115        if let Some(callback) = options[self.on].callback() {
116            callback.call()?;
117        }
118        options[self.on].set_is_selc(true);
119        Ok(true)
120    }
121
122    #[inline]
123    fn have_trigs(&self) -> bool {
124        matches!(
125            (&self.up_trig, &self.down_trig, &self.selc_trig),
126            (Some(_), Some(_), Some(_)))
127    }
128
129    /// Return whether an update occured.
130    pub(crate) fn looper(&mut self, options: &mut Vec<cpn::Option>) -> FtuiResult<bool> {
131        // Hard to avoid using `unwrap`.
132        if !self.have_trigs() {
133            return Err(FtuiError::SelectorNoTriggers);
134        }
135
136        if self.up_trig.as_ref().unwrap().check()? && self.up(options) {
137            Ok(true)
138        } else if self.down_trig.as_ref().unwrap().check()? && self.down(options) {
139            Ok(true)
140        } else if self.selc_trig.as_ref().unwrap().check()? {
141            self.select(options)?;
142            Ok(true)
143        } else {
144            Ok(false)
145        }
146    }
147
148    #[inline]
149    pub fn up_trig_mut(&mut self) -> FtuiResult<&mut Trigger> {
150        self.up_trig.as_mut().ok_or(FtuiError::SelectorNoTriggers)
151    }
152
153    #[inline]
154    pub fn down_trig_mut(&mut self) -> FtuiResult<&mut Trigger> {
155        self.down_trig.as_mut().ok_or(FtuiError::SelectorNoTriggers)
156    }
157
158    #[inline]
159    pub fn select_trig_mut(&mut self) -> FtuiResult<&mut Trigger> {
160        self.selc_trig.as_mut().ok_or(FtuiError::SelectorNoTriggers)
161    }
162
163    /// Update all of the `Selector` triggers argument. 
164    pub fn update_trig_arg<T, U, V>(
165        &mut self, up_arg: T, down_arg: U, selc_arg: V
166    ) -> FtuiResult<()>
167    where
168        T: 'static,
169        U: 'static,
170        V: 'static,
171    {
172        match (&mut self.up_trig, &mut self.down_trig, &mut self.selc_trig) {
173            (Some(u), Some(d), Some(s)) => {
174                u.update_arg(up_arg);
175                d.update_arg(down_arg);
176                s.update_arg(selc_arg);
177                Ok(())
178            }
179            _ => Err(FtuiError::SelectorNoTriggers),
180        }
181    }
182}