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}