slt/context/widgets_display/
text.rs1use super::*;
2use crate::KeyMap;
3
4impl Context {
5 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
16 let content = s.into();
17 let default_fg = self
18 .rollback
19 .text_color_stack
20 .iter()
21 .rev()
22 .find_map(|c| *c)
23 .unwrap_or(self.theme.text);
24 self.commands.push(Command::Text {
25 content,
26 cursor_offset: None,
27 style: Style::new().fg(default_fg),
28 grow: 0,
29 align: Align::Start,
30 wrap: false,
31 truncate: false,
32 margin: Margin::default(),
33 constraints: Constraints::default(),
34 });
35 self.rollback.last_text_idx = Some(self.commands.len() - 1);
36 self
37 }
38
39 #[allow(clippy::print_stderr)]
45 pub fn link(&mut self, text: impl Into<String>, url: impl Into<String>) -> &mut Self {
46 let url_str = url.into();
47 let focused = self.register_focusable();
48 let (_interaction_id, response) = self.begin_widget_interaction(focused);
49
50 let activated = response.clicked || self.consume_activation_keys(focused);
51
52 if activated {
53 if let Err(e) = open_url(&url_str) {
54 eprintln!("[slt] failed to open URL: {e}");
55 }
56 }
57
58 let style = if focused {
59 Style::new()
60 .fg(self.theme.primary)
61 .bg(self.theme.surface_hover)
62 .underline()
63 .bold()
64 } else if response.hovered {
65 Style::new()
66 .fg(self.theme.accent)
67 .bg(self.theme.surface_hover)
68 .underline()
69 } else {
70 Style::new().fg(self.theme.primary).underline()
71 };
72
73 self.commands.push(Command::Link {
74 text: text.into(),
75 url: url_str,
76 style,
77 margin: Margin::default(),
78 constraints: Constraints::default(),
79 });
80 self.rollback.last_text_idx = Some(self.commands.len() - 1);
81 self
82 }
83
84 pub fn timer_display(&mut self, elapsed: std::time::Duration) -> &mut Self {
88 let total_centis = elapsed.as_millis() / 10;
89 let centis = total_centis % 100;
90 let total_seconds = total_centis / 100;
91 let seconds = total_seconds % 60;
92 let minutes = (total_seconds / 60) % 60;
93 let hours = total_seconds / 3600;
94
95 let content = if hours > 0 {
96 format!("{hours:02}:{minutes:02}:{seconds:02}.{centis:02}")
97 } else {
98 format!("{minutes:02}:{seconds:02}.{centis:02}")
99 };
100
101 self.commands.push(Command::Text {
102 content,
103 cursor_offset: None,
104 style: Style::new().fg(self.theme.text),
105 grow: 0,
106 align: Align::Start,
107 wrap: false,
108 truncate: false,
109 margin: Margin::default(),
110 constraints: Constraints::default(),
111 });
112 self.rollback.last_text_idx = Some(self.commands.len() - 1);
113 self
114 }
115
116 pub fn help_from_keymap(&mut self, keymap: &KeyMap) -> Response {
118 let pairs: Vec<(&str, &str)> = keymap
119 .visible_bindings()
120 .map(|binding| (binding.display.as_str(), binding.description.as_str()))
121 .collect();
122 self.help(&pairs)
123 }
124
125 pub fn bold(&mut self) -> &mut Self {
129 self.modify_last_style(|s| s.modifiers |= Modifiers::BOLD);
130 self
131 }
132
133 pub fn dim(&mut self) -> &mut Self {
138 let text_dim = self.theme.text_dim;
139 self.modify_last_style(|s| {
140 s.modifiers |= Modifiers::DIM;
141 if s.fg.is_none() {
142 s.fg = Some(text_dim);
143 }
144 });
145 self
146 }
147
148 pub fn italic(&mut self) -> &mut Self {
150 self.modify_last_style(|s| s.modifiers |= Modifiers::ITALIC);
151 self
152 }
153
154 pub fn underline(&mut self) -> &mut Self {
156 self.modify_last_style(|s| s.modifiers |= Modifiers::UNDERLINE);
157 self
158 }
159
160 pub fn reversed(&mut self) -> &mut Self {
162 self.modify_last_style(|s| s.modifiers |= Modifiers::REVERSED);
163 self
164 }
165
166 pub fn strikethrough(&mut self) -> &mut Self {
168 self.modify_last_style(|s| s.modifiers |= Modifiers::STRIKETHROUGH);
169 self
170 }
171
172 pub fn fg(&mut self, color: Color) -> &mut Self {
174 self.modify_last_style(|s| s.fg = Some(color));
175 self
176 }
177
178 pub fn bg(&mut self, color: Color) -> &mut Self {
180 self.modify_last_style(|s| s.bg = Some(color));
181 self
182 }
183
184 pub fn gradient(&mut self, from: Color, to: Color) -> &mut Self {
186 if let Some(idx) = self.rollback.last_text_idx {
187 let replacement = match &self.commands[idx] {
188 Command::Text {
189 content,
190 style,
191 wrap,
192 align,
193 margin,
194 constraints,
195 ..
196 } => {
197 let chars: Vec<char> = content.chars().collect();
198 let len = chars.len();
199 let denom = len.saturating_sub(1).max(1) as f32;
200 let segments = chars
201 .into_iter()
202 .enumerate()
203 .map(|(i, ch)| {
204 let mut seg_style = *style;
205 seg_style.fg = Some(from.blend(to, i as f32 / denom));
206 (ch.to_string(), seg_style)
207 })
208 .collect();
209
210 Some(Command::RichText {
211 segments,
212 wrap: *wrap,
213 align: *align,
214 margin: *margin,
215 constraints: *constraints,
216 })
217 }
218 _ => None,
219 };
220
221 if let Some(command) = replacement {
222 self.commands[idx] = command;
223 }
224 }
225
226 self
227 }
228
229 pub fn group_hover_fg(&mut self, color: Color) -> &mut Self {
231 let apply_group_style = self
232 .rollback
233 .group_stack
234 .last()
235 .map(|name| self.is_group_hovered(name) || self.is_group_focused(name))
236 .unwrap_or(false);
237 if apply_group_style {
238 self.modify_last_style(|s| s.fg = Some(color));
239 }
240 self
241 }
242
243 pub fn group_hover_bg(&mut self, color: Color) -> &mut Self {
245 let apply_group_style = self
246 .rollback
247 .group_stack
248 .last()
249 .map(|name| self.is_group_hovered(name) || self.is_group_focused(name))
250 .unwrap_or(false);
251 if apply_group_style {
252 self.modify_last_style(|s| s.bg = Some(color));
253 }
254 self
255 }
256
257 pub fn styled(&mut self, s: impl Into<String>, style: Style) -> &mut Self {
262 self.styled_with_cursor(s, style, None)
263 }
264
265 pub(crate) fn styled_with_cursor(
266 &mut self,
267 s: impl Into<String>,
268 style: Style,
269 cursor_offset: Option<usize>,
270 ) -> &mut Self {
271 self.commands.push(Command::Text {
272 content: s.into(),
273 cursor_offset,
274 style,
275 grow: 0,
276 align: Align::Start,
277 wrap: false,
278 truncate: false,
279 margin: Margin::default(),
280 constraints: Constraints::default(),
281 });
282 self.rollback.last_text_idx = Some(self.commands.len() - 1);
283 self
284 }
285
286 pub fn wrap(&mut self) -> &mut Self {
288 if let Some(idx) = self.rollback.last_text_idx {
289 if let Command::Text { wrap, .. } = &mut self.commands[idx] {
290 *wrap = true;
291 }
292 }
293 self
294 }
295
296 pub fn truncate(&mut self) -> &mut Self {
299 if let Some(idx) = self.rollback.last_text_idx {
300 if let Command::Text { truncate, .. } = &mut self.commands[idx] {
301 *truncate = true;
302 }
303 }
304 self
305 }
306
307 fn modify_last_style(&mut self, f: impl FnOnce(&mut Style)) {
308 if let Some(idx) = self.rollback.last_text_idx {
309 match &mut self.commands[idx] {
310 Command::Text { style, .. } | Command::Link { style, .. } => f(style),
311 _ => {}
312 }
313 }
314 }
315
316 fn modify_last_constraints(&mut self, f: impl FnOnce(&mut Constraints)) {
317 if let Some(idx) = self.rollback.last_text_idx {
318 match &mut self.commands[idx] {
319 Command::Text { constraints, .. } | Command::Link { constraints, .. } => {
320 f(constraints)
321 }
322 _ => {}
323 }
324 }
325 }
326
327 fn modify_last_margin(&mut self, f: impl FnOnce(&mut Margin)) {
328 if let Some(idx) = self.rollback.last_text_idx {
329 match &mut self.commands[idx] {
330 Command::Text { margin, .. } | Command::Link { margin, .. } => f(margin),
331 _ => {}
332 }
333 }
334 }
335
336 pub fn grow(&mut self, value: u16) -> &mut Self {
343 if let Some(idx) = self.rollback.last_text_idx {
344 if let Command::Text { grow, .. } = &mut self.commands[idx] {
345 *grow = value;
346 }
347 }
348 self
349 }
350
351 pub fn align(&mut self, align: Align) -> &mut Self {
353 if let Some(idx) = self.rollback.last_text_idx {
354 if let Command::Text {
355 align: text_align, ..
356 } = &mut self.commands[idx]
357 {
358 *text_align = align;
359 }
360 }
361 self
362 }
363
364 pub fn text_center(&mut self) -> &mut Self {
368 self.align(Align::Center)
369 }
370
371 pub fn text_right(&mut self) -> &mut Self {
374 self.align(Align::End)
375 }
376
377 pub fn w(&mut self, value: u32) -> &mut Self {
385 self.modify_last_constraints(|c| {
386 *c = c.w(value);
387 });
388 self
389 }
390
391 pub fn h(&mut self, value: u32) -> &mut Self {
395 self.modify_last_constraints(|c| {
396 *c = c.h(value);
397 });
398 self
399 }
400
401 pub fn min_w(&mut self, value: u32) -> &mut Self {
403 self.modify_last_constraints(|c| c.set_min_width(Some(value)));
404 self
405 }
406
407 pub fn max_w(&mut self, value: u32) -> &mut Self {
409 self.modify_last_constraints(|c| c.set_max_width(Some(value)));
410 self
411 }
412
413 pub fn min_h(&mut self, value: u32) -> &mut Self {
415 self.modify_last_constraints(|c| c.set_min_height(Some(value)));
416 self
417 }
418
419 pub fn max_h(&mut self, value: u32) -> &mut Self {
421 self.modify_last_constraints(|c| c.set_max_height(Some(value)));
422 self
423 }
424
425 pub fn m(&mut self, value: u32) -> &mut Self {
429 self.modify_last_margin(|m| *m = Margin::all(value));
430 self
431 }
432
433 pub fn mx(&mut self, value: u32) -> &mut Self {
435 self.modify_last_margin(|m| {
436 m.left = value;
437 m.right = value;
438 });
439 self
440 }
441
442 pub fn my(&mut self, value: u32) -> &mut Self {
444 self.modify_last_margin(|m| {
445 m.top = value;
446 m.bottom = value;
447 });
448 self
449 }
450
451 pub fn mt(&mut self, value: u32) -> &mut Self {
453 self.modify_last_margin(|m| m.top = value);
454 self
455 }
456
457 pub fn mr(&mut self, value: u32) -> &mut Self {
459 self.modify_last_margin(|m| m.right = value);
460 self
461 }
462
463 pub fn mb(&mut self, value: u32) -> &mut Self {
465 self.modify_last_margin(|m| m.bottom = value);
466 self
467 }
468
469 pub fn ml(&mut self, value: u32) -> &mut Self {
471 self.modify_last_margin(|m| m.left = value);
472 self
473 }
474
475 pub fn spacer(&mut self) -> &mut Self {
479 self.commands.push(Command::Spacer { grow: 1 });
480 self.rollback.last_text_idx = None;
481 self
482 }
483
484 pub fn with_if(&mut self, cond: bool, f: impl FnOnce(&mut Self)) -> &mut Self {
513 if cond {
514 f(self);
515 }
516 self
517 }
518
519 pub fn with(&mut self, f: impl FnOnce(&mut Self)) -> &mut Self {
533 f(self);
534 self
535 }
536}