slt/context/widgets_input/
feedback.rs1use super::*;
2
3impl Context {
4 pub fn spinner(&mut self, state: &SpinnerState) -> Response {
15 let response = self.interaction();
16 self.styled(
17 state.frame(self.tick).to_string(),
18 Style::new().fg(self.theme.primary),
19 );
20 response
21 }
22
23 pub fn toast(&mut self, state: &mut ToastState) -> &mut Self {
28 state.cleanup(self.tick);
29 if state.messages.is_empty() {
30 return self;
31 }
32
33 self.skip_interaction_slot();
34 self.commands
35 .push(Command::BeginContainer(Box::new(BeginContainerArgs {
36 direction: Direction::Column,
37 gap: 0,
38 align: Align::Start,
39 align_self: None,
40 justify: Justify::Start,
41 border: None,
42 border_sides: BorderSides::all(),
43 border_style: Style::new().fg(self.theme.border),
44 bg_color: None,
45 padding: Padding::default(),
46 margin: Margin::default(),
47 constraints: Constraints::default(),
48 title: None,
49 grow: 0,
50 group_name: None,
51 })));
52 for message in state.messages.iter().rev() {
53 let color = match message.level {
54 ToastLevel::Info => self.theme.primary,
55 ToastLevel::Success => self.theme.success,
56 ToastLevel::Warning => self.theme.warning,
57 ToastLevel::Error => self.theme.error,
58 };
59 let mut line = String::with_capacity(4 + message.text.len());
60 line.push_str(" ● ");
61 line.push_str(&message.text);
62 self.styled(line, Style::new().fg(color));
63 }
64 self.commands.push(Command::EndContainer);
65 self.rollback.last_text_idx = None;
66
67 self
68 }
69
70 pub fn slider(
85 &mut self,
86 label: &str,
87 value: &mut f64,
88 range: std::ops::RangeInclusive<f64>,
89 ) -> Response {
90 let span = (*range.end() - *range.start()).max(0.0);
91 let step = if span > 0.0 { span / 20.0 } else { 0.0 };
92 self.slider_inner(label, value, range, step)
93 }
94
95 pub fn slider_with_step(
110 &mut self,
111 label: &str,
112 value: &mut f64,
113 range: std::ops::RangeInclusive<f64>,
114 step: f64,
115 ) -> Response {
116 self.slider_inner(label, value, range, step.max(0.0))
117 }
118
119 fn slider_inner(
120 &mut self,
121 label: &str,
122 value: &mut f64,
123 range: std::ops::RangeInclusive<f64>,
124 step: f64,
125 ) -> Response {
126 let focused = self.register_focusable();
127 let mut changed = false;
128
129 let start = *range.start();
130 let end = *range.end();
131 let span = (end - start).max(0.0);
132
133 *value = (*value).clamp(start, end);
134
135 if focused {
136 let mut consumed_indices = Vec::new();
137 for (i, key) in self.available_key_presses() {
138 match key.code {
139 KeyCode::Left | KeyCode::Char('h') => {
140 if step > 0.0 {
141 let next = (*value - step).max(start);
142 if (next - *value).abs() > f64::EPSILON {
143 *value = next;
144 changed = true;
145 }
146 }
147 consumed_indices.push(i);
148 }
149 KeyCode::Right | KeyCode::Char('l') => {
150 if step > 0.0 {
151 let next = (*value + step).min(end);
152 if (next - *value).abs() > f64::EPSILON {
153 *value = next;
154 changed = true;
155 }
156 }
157 consumed_indices.push(i);
158 }
159 _ => {}
160 }
161 }
162 self.consume_indices(consumed_indices);
163 }
164
165 let ratio = if span <= f64::EPSILON {
166 0.0
167 } else {
168 ((*value - start) / span).clamp(0.0, 1.0)
169 };
170
171 let value_text = format_compact_number(*value);
172 let label_width = UnicodeWidthStr::width(label) as u32;
173 let value_width = UnicodeWidthStr::width(value_text.as_str()) as u32;
174 let track_width = self
175 .area_width
176 .saturating_sub(label_width + value_width + 8)
177 .max(10) as usize;
178 let thumb_idx = if track_width <= 1 {
179 0
180 } else {
181 (ratio * (track_width as f64 - 1.0)).round() as usize
182 };
183
184 let mut track = String::with_capacity(track_width);
185 for i in 0..track_width {
186 if i == thumb_idx {
187 track.push('○');
188 } else if i < thumb_idx {
189 track.push('█');
190 } else {
191 track.push('━');
192 }
193 }
194
195 let text_color = self.theme.text;
196 let border_color = self.theme.border;
197 let primary_color = self.theme.primary;
198 let dim_color = self.theme.text_dim;
199 let mut response = self.container().row(|ui| {
200 ui.text(label).fg(text_color);
201 ui.text("[").fg(border_color);
202 ui.text(track).grow(1).fg(primary_color);
203 ui.text("]").fg(border_color);
204 if focused {
205 ui.text(value_text.as_str()).bold().fg(primary_color);
206 } else {
207 ui.text(value_text.as_str()).fg(dim_color);
208 }
209 });
210 response.focused = focused;
211 response.changed = changed;
212 response
213 }
214}