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 .text_color_stack
19 .iter()
20 .rev()
21 .find_map(|c| *c)
22 .unwrap_or(self.theme.text);
23 self.commands.push(Command::Text {
24 content,
25 cursor_offset: None,
26 style: Style::new().fg(default_fg),
27 grow: 0,
28 align: Align::Start,
29 wrap: false,
30 truncate: false,
31 margin: Margin::default(),
32 constraints: Constraints::default(),
33 });
34 self.last_text_idx = Some(self.commands.len() - 1);
35 self
36 }
37
38 #[allow(clippy::print_stderr)]
44 pub fn link(&mut self, text: impl Into<String>, url: impl Into<String>) -> &mut Self {
45 let url_str = url.into();
46 let focused = self.register_focusable();
47 let interaction_id = self.next_interaction_id();
48 let response = self.response_for(interaction_id);
49
50 let mut activated = response.clicked;
51 if focused {
52 for (i, event) in self.events.iter().enumerate() {
53 if let Event::Key(key) = event {
54 if key.kind != KeyEventKind::Press {
55 continue;
56 }
57 if matches!(key.code, KeyCode::Enter | KeyCode::Char(' ')) {
58 activated = true;
59 self.consumed[i] = true;
60 }
61 }
62 }
63 }
64
65 if activated {
66 if let Err(e) = open_url(&url_str) {
67 eprintln!("[slt] failed to open URL: {e}");
68 }
69 }
70
71 let style = if focused {
72 Style::new()
73 .fg(self.theme.primary)
74 .bg(self.theme.surface_hover)
75 .underline()
76 .bold()
77 } else if response.hovered {
78 Style::new()
79 .fg(self.theme.accent)
80 .bg(self.theme.surface_hover)
81 .underline()
82 } else {
83 Style::new().fg(self.theme.primary).underline()
84 };
85
86 self.commands.push(Command::Link {
87 text: text.into(),
88 url: url_str,
89 style,
90 margin: Margin::default(),
91 constraints: Constraints::default(),
92 });
93 self.last_text_idx = Some(self.commands.len() - 1);
94 self
95 }
96
97 #[deprecated(since = "0.15.4", note = "use ui.text(s).wrap() instead")]
105 pub fn text_wrap(&mut self, s: impl Into<String>) -> &mut Self {
106 let content = s.into();
107 let default_fg = self
108 .text_color_stack
109 .iter()
110 .rev()
111 .find_map(|c| *c)
112 .unwrap_or(self.theme.text);
113 self.commands.push(Command::Text {
114 content,
115 cursor_offset: None,
116 style: Style::new().fg(default_fg),
117 grow: 0,
118 align: Align::Start,
119 wrap: true,
120 truncate: false,
121 margin: Margin::default(),
122 constraints: Constraints::default(),
123 });
124 self.last_text_idx = Some(self.commands.len() - 1);
125 self
126 }
127
128 pub fn timer_display(&mut self, elapsed: std::time::Duration) -> &mut Self {
132 let total_centis = elapsed.as_millis() / 10;
133 let centis = total_centis % 100;
134 let total_seconds = total_centis / 100;
135 let seconds = total_seconds % 60;
136 let minutes = (total_seconds / 60) % 60;
137 let hours = total_seconds / 3600;
138
139 let content = if hours > 0 {
140 format!("{hours:02}:{minutes:02}:{seconds:02}.{centis:02}")
141 } else {
142 format!("{minutes:02}:{seconds:02}.{centis:02}")
143 };
144
145 self.commands.push(Command::Text {
146 content,
147 cursor_offset: None,
148 style: Style::new().fg(self.theme.text),
149 grow: 0,
150 align: Align::Start,
151 wrap: false,
152 truncate: false,
153 margin: Margin::default(),
154 constraints: Constraints::default(),
155 });
156 self.last_text_idx = Some(self.commands.len() - 1);
157 self
158 }
159
160 pub fn help_from_keymap(&mut self, keymap: &KeyMap) -> Response {
162 let pairs: Vec<(&str, &str)> = keymap
163 .visible_bindings()
164 .map(|binding| (binding.display.as_str(), binding.description.as_str()))
165 .collect();
166 self.help(&pairs)
167 }
168
169 pub fn bold(&mut self) -> &mut Self {
173 self.modify_last_style(|s| s.modifiers |= Modifiers::BOLD);
174 self
175 }
176
177 pub fn dim(&mut self) -> &mut Self {
182 let text_dim = self.theme.text_dim;
183 self.modify_last_style(|s| {
184 s.modifiers |= Modifiers::DIM;
185 if s.fg.is_none() {
186 s.fg = Some(text_dim);
187 }
188 });
189 self
190 }
191
192 pub fn italic(&mut self) -> &mut Self {
194 self.modify_last_style(|s| s.modifiers |= Modifiers::ITALIC);
195 self
196 }
197
198 pub fn underline(&mut self) -> &mut Self {
200 self.modify_last_style(|s| s.modifiers |= Modifiers::UNDERLINE);
201 self
202 }
203
204 pub fn reversed(&mut self) -> &mut Self {
206 self.modify_last_style(|s| s.modifiers |= Modifiers::REVERSED);
207 self
208 }
209
210 pub fn strikethrough(&mut self) -> &mut Self {
212 self.modify_last_style(|s| s.modifiers |= Modifiers::STRIKETHROUGH);
213 self
214 }
215
216 pub fn fg(&mut self, color: Color) -> &mut Self {
218 self.modify_last_style(|s| s.fg = Some(color));
219 self
220 }
221
222 pub fn bg(&mut self, color: Color) -> &mut Self {
224 self.modify_last_style(|s| s.bg = Some(color));
225 self
226 }
227
228 pub fn gradient(&mut self, from: Color, to: Color) -> &mut Self {
230 if let Some(idx) = self.last_text_idx {
231 let replacement = match &self.commands[idx] {
232 Command::Text {
233 content,
234 style,
235 wrap,
236 align,
237 margin,
238 constraints,
239 ..
240 } => {
241 let chars: Vec<char> = content.chars().collect();
242 let len = chars.len();
243 let denom = len.saturating_sub(1).max(1) as f32;
244 let segments = chars
245 .into_iter()
246 .enumerate()
247 .map(|(i, ch)| {
248 let mut seg_style = *style;
249 seg_style.fg = Some(from.blend(to, i as f32 / denom));
250 (ch.to_string(), seg_style)
251 })
252 .collect();
253
254 Some(Command::RichText {
255 segments,
256 wrap: *wrap,
257 align: *align,
258 margin: *margin,
259 constraints: *constraints,
260 })
261 }
262 _ => None,
263 };
264
265 if let Some(command) = replacement {
266 self.commands[idx] = command;
267 }
268 }
269
270 self
271 }
272
273 pub fn group_hover_fg(&mut self, color: Color) -> &mut Self {
275 let apply_group_style = self
276 .group_stack
277 .last()
278 .map(|name| self.is_group_hovered(name) || self.is_group_focused(name))
279 .unwrap_or(false);
280 if apply_group_style {
281 self.modify_last_style(|s| s.fg = Some(color));
282 }
283 self
284 }
285
286 pub fn group_hover_bg(&mut self, color: Color) -> &mut Self {
288 let apply_group_style = self
289 .group_stack
290 .last()
291 .map(|name| self.is_group_hovered(name) || self.is_group_focused(name))
292 .unwrap_or(false);
293 if apply_group_style {
294 self.modify_last_style(|s| s.bg = Some(color));
295 }
296 self
297 }
298
299 pub fn styled(&mut self, s: impl Into<String>, style: Style) -> &mut Self {
304 self.styled_with_cursor(s, style, None)
305 }
306
307 pub(crate) fn styled_with_cursor(
308 &mut self,
309 s: impl Into<String>,
310 style: Style,
311 cursor_offset: Option<usize>,
312 ) -> &mut Self {
313 self.commands.push(Command::Text {
314 content: s.into(),
315 cursor_offset,
316 style,
317 grow: 0,
318 align: Align::Start,
319 wrap: false,
320 truncate: false,
321 margin: Margin::default(),
322 constraints: Constraints::default(),
323 });
324 self.last_text_idx = Some(self.commands.len() - 1);
325 self
326 }
327
328 pub fn wrap(&mut self) -> &mut Self {
330 if let Some(idx) = self.last_text_idx {
331 if let Command::Text { wrap, .. } = &mut self.commands[idx] {
332 *wrap = true;
333 }
334 }
335 self
336 }
337
338 pub fn truncate(&mut self) -> &mut Self {
341 if let Some(idx) = self.last_text_idx {
342 if let Command::Text { truncate, .. } = &mut self.commands[idx] {
343 *truncate = true;
344 }
345 }
346 self
347 }
348
349 fn modify_last_style(&mut self, f: impl FnOnce(&mut Style)) {
350 if let Some(idx) = self.last_text_idx {
351 match &mut self.commands[idx] {
352 Command::Text { style, .. } | Command::Link { style, .. } => f(style),
353 _ => {}
354 }
355 }
356 }
357
358 fn modify_last_constraints(&mut self, f: impl FnOnce(&mut Constraints)) {
359 if let Some(idx) = self.last_text_idx {
360 match &mut self.commands[idx] {
361 Command::Text { constraints, .. } | Command::Link { constraints, .. } => {
362 f(constraints)
363 }
364 _ => {}
365 }
366 }
367 }
368
369 fn modify_last_margin(&mut self, f: impl FnOnce(&mut Margin)) {
370 if let Some(idx) = self.last_text_idx {
371 match &mut self.commands[idx] {
372 Command::Text { margin, .. } | Command::Link { margin, .. } => f(margin),
373 _ => {}
374 }
375 }
376 }
377
378 pub fn grow(&mut self, value: u16) -> &mut Self {
385 if let Some(idx) = self.last_text_idx {
386 if let Command::Text { grow, .. } = &mut self.commands[idx] {
387 *grow = value;
388 }
389 }
390 self
391 }
392
393 pub fn align(&mut self, align: Align) -> &mut Self {
395 if let Some(idx) = self.last_text_idx {
396 if let Command::Text {
397 align: text_align, ..
398 } = &mut self.commands[idx]
399 {
400 *text_align = align;
401 }
402 }
403 self
404 }
405
406 pub fn text_center(&mut self) -> &mut Self {
410 self.align(Align::Center)
411 }
412
413 pub fn text_right(&mut self) -> &mut Self {
416 self.align(Align::End)
417 }
418
419 pub fn w(&mut self, value: u32) -> &mut Self {
426 self.modify_last_constraints(|c| {
427 c.min_width = Some(value);
428 c.max_width = Some(value);
429 });
430 self
431 }
432
433 pub fn h(&mut self, value: u32) -> &mut Self {
437 self.modify_last_constraints(|c| {
438 c.min_height = Some(value);
439 c.max_height = Some(value);
440 });
441 self
442 }
443
444 pub fn min_w(&mut self, value: u32) -> &mut Self {
446 self.modify_last_constraints(|c| c.min_width = Some(value));
447 self
448 }
449
450 pub fn max_w(&mut self, value: u32) -> &mut Self {
452 self.modify_last_constraints(|c| c.max_width = Some(value));
453 self
454 }
455
456 pub fn min_h(&mut self, value: u32) -> &mut Self {
458 self.modify_last_constraints(|c| c.min_height = Some(value));
459 self
460 }
461
462 pub fn max_h(&mut self, value: u32) -> &mut Self {
464 self.modify_last_constraints(|c| c.max_height = Some(value));
465 self
466 }
467
468 pub fn m(&mut self, value: u32) -> &mut Self {
472 self.modify_last_margin(|m| *m = Margin::all(value));
473 self
474 }
475
476 pub fn mx(&mut self, value: u32) -> &mut Self {
478 self.modify_last_margin(|m| {
479 m.left = value;
480 m.right = value;
481 });
482 self
483 }
484
485 pub fn my(&mut self, value: u32) -> &mut Self {
487 self.modify_last_margin(|m| {
488 m.top = value;
489 m.bottom = value;
490 });
491 self
492 }
493
494 pub fn mt(&mut self, value: u32) -> &mut Self {
496 self.modify_last_margin(|m| m.top = value);
497 self
498 }
499
500 pub fn mr(&mut self, value: u32) -> &mut Self {
502 self.modify_last_margin(|m| m.right = value);
503 self
504 }
505
506 pub fn mb(&mut self, value: u32) -> &mut Self {
508 self.modify_last_margin(|m| m.bottom = value);
509 self
510 }
511
512 pub fn ml(&mut self, value: u32) -> &mut Self {
514 self.modify_last_margin(|m| m.left = value);
515 self
516 }
517
518 pub fn spacer(&mut self) -> &mut Self {
522 self.commands.push(Command::Spacer { grow: 1 });
523 self.last_text_idx = None;
524 self
525 }
526}