1use crate::chart::{build_histogram_config, render_chart, ChartBuilder, HistogramBuilder};
2use crate::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseKind};
3use crate::halfblock::HalfBlockImage;
4use crate::layout::{Command, Direction};
5use crate::rect::Rect;
6use crate::style::{
7 Align, Border, BorderSides, Breakpoint, Color, Constraints, ContainerStyle, Justify, Margin,
8 Modifiers, Padding, Style, Theme,
9};
10use crate::widgets::{
11 ApprovalAction, ButtonVariant, CommandPaletteState, ContextItem, FormField, FormState,
12 ListState, MultiSelectState, RadioState, ScrollState, SelectState, SpinnerState,
13 StreamingTextState, TableState, TabsState, TextInputState, TextareaState, ToastLevel,
14 ToastState, ToolApprovalState, TreeState,
15};
16use crate::FrameState;
17use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
18
19#[allow(dead_code)]
20fn slt_assert(condition: bool, msg: &str) {
21 if !condition {
22 panic!("[SLT] {}", msg);
23 }
24}
25
26#[cfg(debug_assertions)]
27#[allow(dead_code)]
28fn slt_warn(msg: &str) {
29 eprintln!("\x1b[33m[SLT warning]\x1b[0m {}", msg);
30}
31
32#[cfg(not(debug_assertions))]
33#[allow(dead_code)]
34fn slt_warn(_msg: &str) {}
35
36#[derive(Debug, Copy, Clone, PartialEq, Eq)]
38pub struct State<T> {
39 idx: usize,
40 _marker: std::marker::PhantomData<T>,
41}
42
43impl<T: 'static> State<T> {
44 pub fn get<'a>(&self, ui: &'a Context) -> &'a T {
46 ui.hook_states[self.idx]
47 .downcast_ref::<T>()
48 .unwrap_or_else(|| {
49 panic!(
50 "use_state type mismatch at hook index {} — expected {}",
51 self.idx,
52 std::any::type_name::<T>()
53 )
54 })
55 }
56
57 pub fn get_mut<'a>(&self, ui: &'a mut Context) -> &'a mut T {
59 ui.hook_states[self.idx]
60 .downcast_mut::<T>()
61 .unwrap_or_else(|| {
62 panic!(
63 "use_state type mismatch at hook index {} — expected {}",
64 self.idx,
65 std::any::type_name::<T>()
66 )
67 })
68 }
69}
70
71#[derive(Debug, Clone, Copy, Default)]
77pub struct Response {
78 pub clicked: bool,
80 pub hovered: bool,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum BarDirection {
87 Horizontal,
89 Vertical,
91}
92
93#[derive(Debug, Clone)]
95pub struct Bar {
96 pub label: String,
98 pub value: f64,
100 pub color: Option<Color>,
102}
103
104impl Bar {
105 pub fn new(label: impl Into<String>, value: f64) -> Self {
107 Self {
108 label: label.into(),
109 value,
110 color: None,
111 }
112 }
113
114 pub fn color(mut self, color: Color) -> Self {
116 self.color = Some(color);
117 self
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct BarGroup {
124 pub label: String,
126 pub bars: Vec<Bar>,
128}
129
130impl BarGroup {
131 pub fn new(label: impl Into<String>, bars: Vec<Bar>) -> Self {
133 Self {
134 label: label.into(),
135 bars,
136 }
137 }
138}
139
140pub trait Widget {
202 type Response;
205
206 fn ui(&mut self, ctx: &mut Context) -> Self::Response;
212}
213
214pub struct Context {
230 pub(crate) commands: Vec<Command>,
232 pub(crate) events: Vec<Event>,
233 pub(crate) consumed: Vec<bool>,
234 pub(crate) should_quit: bool,
235 pub(crate) area_width: u32,
236 pub(crate) area_height: u32,
237 pub(crate) tick: u64,
238 pub(crate) focus_index: usize,
239 pub(crate) focus_count: usize,
240 pub(crate) hook_states: Vec<Box<dyn std::any::Any>>,
241 pub(crate) hook_cursor: usize,
242 prev_focus_count: usize,
243 scroll_count: usize,
244 prev_scroll_infos: Vec<(u32, u32)>,
245 prev_scroll_rects: Vec<Rect>,
246 interaction_count: usize,
247 pub(crate) prev_hit_map: Vec<Rect>,
248 pub(crate) group_stack: Vec<String>,
249 pub(crate) prev_group_rects: Vec<(String, Rect)>,
250 group_count: usize,
251 prev_focus_groups: Vec<Option<String>>,
252 _prev_focus_rects: Vec<(usize, Rect)>,
253 mouse_pos: Option<(u32, u32)>,
254 click_pos: Option<(u32, u32)>,
255 last_text_idx: Option<usize>,
256 overlay_depth: usize,
257 pub(crate) modal_active: bool,
258 prev_modal_active: bool,
259 pub(crate) clipboard_text: Option<String>,
260 debug: bool,
261 theme: Theme,
262 pub(crate) dark_mode: bool,
263 pub(crate) is_real_terminal: bool,
264 pub(crate) deferred_draws: Vec<Option<RawDrawCallback>>,
265}
266
267type RawDrawCallback = Box<dyn FnOnce(&mut crate::buffer::Buffer, Rect)>;
268
269struct ContextSnapshot {
270 cmd_count: usize,
271 last_text_idx: Option<usize>,
272 focus_count: usize,
273 interaction_count: usize,
274 scroll_count: usize,
275 group_count: usize,
276 group_stack_len: usize,
277 overlay_depth: usize,
278 modal_active: bool,
279 hook_cursor: usize,
280 hook_states_len: usize,
281 dark_mode: bool,
282 deferred_draws_len: usize,
283}
284
285impl ContextSnapshot {
286 fn capture(ctx: &Context) -> Self {
287 Self {
288 cmd_count: ctx.commands.len(),
289 last_text_idx: ctx.last_text_idx,
290 focus_count: ctx.focus_count,
291 interaction_count: ctx.interaction_count,
292 scroll_count: ctx.scroll_count,
293 group_count: ctx.group_count,
294 group_stack_len: ctx.group_stack.len(),
295 overlay_depth: ctx.overlay_depth,
296 modal_active: ctx.modal_active,
297 hook_cursor: ctx.hook_cursor,
298 hook_states_len: ctx.hook_states.len(),
299 dark_mode: ctx.dark_mode,
300 deferred_draws_len: ctx.deferred_draws.len(),
301 }
302 }
303
304 fn restore(&self, ctx: &mut Context) {
305 ctx.commands.truncate(self.cmd_count);
306 ctx.last_text_idx = self.last_text_idx;
307 ctx.focus_count = self.focus_count;
308 ctx.interaction_count = self.interaction_count;
309 ctx.scroll_count = self.scroll_count;
310 ctx.group_count = self.group_count;
311 ctx.group_stack.truncate(self.group_stack_len);
312 ctx.overlay_depth = self.overlay_depth;
313 ctx.modal_active = self.modal_active;
314 ctx.hook_cursor = self.hook_cursor;
315 ctx.hook_states.truncate(self.hook_states_len);
316 ctx.dark_mode = self.dark_mode;
317 ctx.deferred_draws.truncate(self.deferred_draws_len);
318 }
319}
320
321#[must_use = "ContainerBuilder does nothing until .col(), .row(), .line(), or .draw() is called"]
342pub struct ContainerBuilder<'a> {
343 ctx: &'a mut Context,
344 gap: u32,
345 align: Align,
346 justify: Justify,
347 border: Option<Border>,
348 border_sides: BorderSides,
349 border_style: Style,
350 bg: Option<Color>,
351 dark_bg: Option<Color>,
352 dark_border_style: Option<Style>,
353 group_hover_bg: Option<Color>,
354 group_hover_border_style: Option<Style>,
355 group_name: Option<String>,
356 padding: Padding,
357 margin: Margin,
358 constraints: Constraints,
359 title: Option<(String, Style)>,
360 grow: u16,
361 scroll_offset: Option<u32>,
362}
363
364#[derive(Debug, Clone, Copy)]
371struct CanvasPixel {
372 bits: u32,
373 color: Color,
374}
375
376#[derive(Debug, Clone)]
378struct CanvasLabel {
379 x: usize,
380 y: usize,
381 text: String,
382 color: Color,
383}
384
385#[derive(Debug, Clone)]
387struct CanvasLayer {
388 grid: Vec<Vec<CanvasPixel>>,
389 labels: Vec<CanvasLabel>,
390}
391
392pub struct CanvasContext {
393 layers: Vec<CanvasLayer>,
394 cols: usize,
395 rows: usize,
396 px_w: usize,
397 px_h: usize,
398 current_color: Color,
399}
400
401impl CanvasContext {
402 fn new(cols: usize, rows: usize) -> Self {
403 Self {
404 layers: vec![Self::new_layer(cols, rows)],
405 cols,
406 rows,
407 px_w: cols * 2,
408 px_h: rows * 4,
409 current_color: Color::Reset,
410 }
411 }
412
413 fn new_layer(cols: usize, rows: usize) -> CanvasLayer {
414 CanvasLayer {
415 grid: vec![
416 vec![
417 CanvasPixel {
418 bits: 0,
419 color: Color::Reset,
420 };
421 cols
422 ];
423 rows
424 ],
425 labels: Vec::new(),
426 }
427 }
428
429 fn current_layer_mut(&mut self) -> Option<&mut CanvasLayer> {
430 self.layers.last_mut()
431 }
432
433 fn dot_with_color(&mut self, x: usize, y: usize, color: Color) {
434 if x >= self.px_w || y >= self.px_h {
435 return;
436 }
437
438 let char_col = x / 2;
439 let char_row = y / 4;
440 let sub_col = x % 2;
441 let sub_row = y % 4;
442 const LEFT_BITS: [u32; 4] = [0x01, 0x02, 0x04, 0x40];
443 const RIGHT_BITS: [u32; 4] = [0x08, 0x10, 0x20, 0x80];
444
445 let bit = if sub_col == 0 {
446 LEFT_BITS[sub_row]
447 } else {
448 RIGHT_BITS[sub_row]
449 };
450
451 if let Some(layer) = self.current_layer_mut() {
452 let cell = &mut layer.grid[char_row][char_col];
453 let new_bits = cell.bits | bit;
454 if new_bits != cell.bits {
455 cell.bits = new_bits;
456 cell.color = color;
457 }
458 }
459 }
460
461 fn dot_isize(&mut self, x: isize, y: isize) {
462 if x >= 0 && y >= 0 {
463 self.dot(x as usize, y as usize);
464 }
465 }
466
467 pub fn width(&self) -> usize {
469 self.px_w
470 }
471
472 pub fn height(&self) -> usize {
474 self.px_h
475 }
476
477 pub fn dot(&mut self, x: usize, y: usize) {
479 self.dot_with_color(x, y, self.current_color);
480 }
481
482 pub fn line(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) {
484 let (mut x, mut y) = (x0 as isize, y0 as isize);
485 let (x1, y1) = (x1 as isize, y1 as isize);
486 let dx = (x1 - x).abs();
487 let dy = -(y1 - y).abs();
488 let sx = if x < x1 { 1 } else { -1 };
489 let sy = if y < y1 { 1 } else { -1 };
490 let mut err = dx + dy;
491
492 loop {
493 self.dot_isize(x, y);
494 if x == x1 && y == y1 {
495 break;
496 }
497 let e2 = 2 * err;
498 if e2 >= dy {
499 err += dy;
500 x += sx;
501 }
502 if e2 <= dx {
503 err += dx;
504 y += sy;
505 }
506 }
507 }
508
509 pub fn rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
511 if w == 0 || h == 0 {
512 return;
513 }
514
515 self.line(x, y, x + w.saturating_sub(1), y);
516 self.line(
517 x + w.saturating_sub(1),
518 y,
519 x + w.saturating_sub(1),
520 y + h.saturating_sub(1),
521 );
522 self.line(
523 x + w.saturating_sub(1),
524 y + h.saturating_sub(1),
525 x,
526 y + h.saturating_sub(1),
527 );
528 self.line(x, y + h.saturating_sub(1), x, y);
529 }
530
531 pub fn circle(&mut self, cx: usize, cy: usize, r: usize) {
533 let mut x = r as isize;
534 let mut y: isize = 0;
535 let mut err: isize = 1 - x;
536 let (cx, cy) = (cx as isize, cy as isize);
537
538 while x >= y {
539 for &(dx, dy) in &[
540 (x, y),
541 (y, x),
542 (-x, y),
543 (-y, x),
544 (x, -y),
545 (y, -x),
546 (-x, -y),
547 (-y, -x),
548 ] {
549 let px = cx + dx;
550 let py = cy + dy;
551 self.dot_isize(px, py);
552 }
553
554 y += 1;
555 if err < 0 {
556 err += 2 * y + 1;
557 } else {
558 x -= 1;
559 err += 2 * (y - x) + 1;
560 }
561 }
562 }
563
564 pub fn set_color(&mut self, color: Color) {
566 self.current_color = color;
567 }
568
569 pub fn color(&self) -> Color {
571 self.current_color
572 }
573
574 pub fn filled_rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
576 if w == 0 || h == 0 {
577 return;
578 }
579
580 let x_end = x.saturating_add(w).min(self.px_w);
581 let y_end = y.saturating_add(h).min(self.px_h);
582 if x >= x_end || y >= y_end {
583 return;
584 }
585
586 for yy in y..y_end {
587 self.line(x, yy, x_end.saturating_sub(1), yy);
588 }
589 }
590
591 pub fn filled_circle(&mut self, cx: usize, cy: usize, r: usize) {
593 let (cx, cy, r) = (cx as isize, cy as isize, r as isize);
594 for y in (cy - r)..=(cy + r) {
595 let dy = y - cy;
596 let span_sq = (r * r - dy * dy).max(0);
597 let dx = (span_sq as f64).sqrt() as isize;
598 for x in (cx - dx)..=(cx + dx) {
599 self.dot_isize(x, y);
600 }
601 }
602 }
603
604 pub fn triangle(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, x2: usize, y2: usize) {
606 self.line(x0, y0, x1, y1);
607 self.line(x1, y1, x2, y2);
608 self.line(x2, y2, x0, y0);
609 }
610
611 pub fn filled_triangle(
613 &mut self,
614 x0: usize,
615 y0: usize,
616 x1: usize,
617 y1: usize,
618 x2: usize,
619 y2: usize,
620 ) {
621 let vertices = [
622 (x0 as isize, y0 as isize),
623 (x1 as isize, y1 as isize),
624 (x2 as isize, y2 as isize),
625 ];
626 let min_y = vertices.iter().map(|(_, y)| *y).min().unwrap_or(0);
627 let max_y = vertices.iter().map(|(_, y)| *y).max().unwrap_or(-1);
628
629 for y in min_y..=max_y {
630 let mut intersections: Vec<f64> = Vec::new();
631
632 for edge in [(0usize, 1usize), (1usize, 2usize), (2usize, 0usize)] {
633 let (x_a, y_a) = vertices[edge.0];
634 let (x_b, y_b) = vertices[edge.1];
635 if y_a == y_b {
636 continue;
637 }
638
639 let (x_start, y_start, x_end, y_end) = if y_a < y_b {
640 (x_a, y_a, x_b, y_b)
641 } else {
642 (x_b, y_b, x_a, y_a)
643 };
644
645 if y < y_start || y >= y_end {
646 continue;
647 }
648
649 let t = (y - y_start) as f64 / (y_end - y_start) as f64;
650 intersections.push(x_start as f64 + t * (x_end - x_start) as f64);
651 }
652
653 intersections.sort_by(|a, b| a.total_cmp(b));
654 let mut i = 0usize;
655 while i + 1 < intersections.len() {
656 let x_start = intersections[i].ceil() as isize;
657 let x_end = intersections[i + 1].floor() as isize;
658 for x in x_start..=x_end {
659 self.dot_isize(x, y);
660 }
661 i += 2;
662 }
663 }
664
665 self.triangle(x0, y0, x1, y1, x2, y2);
666 }
667
668 pub fn points(&mut self, pts: &[(usize, usize)]) {
670 for &(x, y) in pts {
671 self.dot(x, y);
672 }
673 }
674
675 pub fn polyline(&mut self, pts: &[(usize, usize)]) {
677 for window in pts.windows(2) {
678 if let [(x0, y0), (x1, y1)] = window {
679 self.line(*x0, *y0, *x1, *y1);
680 }
681 }
682 }
683
684 pub fn print(&mut self, x: usize, y: usize, text: &str) {
687 if text.is_empty() {
688 return;
689 }
690
691 let color = self.current_color;
692 if let Some(layer) = self.current_layer_mut() {
693 layer.labels.push(CanvasLabel {
694 x,
695 y,
696 text: text.to_string(),
697 color,
698 });
699 }
700 }
701
702 pub fn layer(&mut self) {
704 self.layers.push(Self::new_layer(self.cols, self.rows));
705 }
706
707 pub(crate) fn render(&self) -> Vec<Vec<(String, Color)>> {
708 let mut final_grid = vec![
709 vec![
710 CanvasPixel {
711 bits: 0,
712 color: Color::Reset,
713 };
714 self.cols
715 ];
716 self.rows
717 ];
718 let mut labels_overlay: Vec<Vec<Option<(char, Color)>>> =
719 vec![vec![None; self.cols]; self.rows];
720
721 for layer in &self.layers {
722 for (row, final_row) in final_grid.iter_mut().enumerate().take(self.rows) {
723 for (col, dst) in final_row.iter_mut().enumerate().take(self.cols) {
724 let src = layer.grid[row][col];
725 if src.bits == 0 {
726 continue;
727 }
728
729 let merged = dst.bits | src.bits;
730 if merged != dst.bits {
731 dst.bits = merged;
732 dst.color = src.color;
733 }
734 }
735 }
736
737 for label in &layer.labels {
738 let row = label.y / 4;
739 if row >= self.rows {
740 continue;
741 }
742 let start_col = label.x / 2;
743 for (offset, ch) in label.text.chars().enumerate() {
744 let col = start_col + offset;
745 if col >= self.cols {
746 break;
747 }
748 labels_overlay[row][col] = Some((ch, label.color));
749 }
750 }
751 }
752
753 let mut lines: Vec<Vec<(String, Color)>> = Vec::with_capacity(self.rows);
754 for row in 0..self.rows {
755 let mut segments: Vec<(String, Color)> = Vec::new();
756 let mut current_color: Option<Color> = None;
757 let mut current_text = String::new();
758
759 for col in 0..self.cols {
760 let (ch, color) = if let Some((label_ch, label_color)) = labels_overlay[row][col] {
761 (label_ch, label_color)
762 } else {
763 let bits = final_grid[row][col].bits;
764 let ch = char::from_u32(0x2800 + bits).unwrap_or(' ');
765 (ch, final_grid[row][col].color)
766 };
767
768 match current_color {
769 Some(c) if c == color => {
770 current_text.push(ch);
771 }
772 Some(c) => {
773 segments.push((std::mem::take(&mut current_text), c));
774 current_text.push(ch);
775 current_color = Some(color);
776 }
777 None => {
778 current_text.push(ch);
779 current_color = Some(color);
780 }
781 }
782 }
783
784 if let Some(color) = current_color {
785 segments.push((current_text, color));
786 }
787 lines.push(segments);
788 }
789
790 lines
791 }
792}
793
794impl<'a> ContainerBuilder<'a> {
795 pub fn apply(mut self, style: &ContainerStyle) -> Self {
800 if let Some(v) = style.border {
801 self.border = Some(v);
802 }
803 if let Some(v) = style.border_sides {
804 self.border_sides = v;
805 }
806 if let Some(v) = style.border_style {
807 self.border_style = v;
808 }
809 if let Some(v) = style.bg {
810 self.bg = Some(v);
811 }
812 if let Some(v) = style.dark_bg {
813 self.dark_bg = Some(v);
814 }
815 if let Some(v) = style.dark_border_style {
816 self.dark_border_style = Some(v);
817 }
818 if let Some(v) = style.padding {
819 self.padding = v;
820 }
821 if let Some(v) = style.margin {
822 self.margin = v;
823 }
824 if let Some(v) = style.gap {
825 self.gap = v;
826 }
827 if let Some(v) = style.grow {
828 self.grow = v;
829 }
830 if let Some(v) = style.align {
831 self.align = v;
832 }
833 if let Some(v) = style.justify {
834 self.justify = v;
835 }
836 if let Some(w) = style.w {
837 self.constraints.min_width = Some(w);
838 self.constraints.max_width = Some(w);
839 }
840 if let Some(h) = style.h {
841 self.constraints.min_height = Some(h);
842 self.constraints.max_height = Some(h);
843 }
844 if let Some(v) = style.min_w {
845 self.constraints.min_width = Some(v);
846 }
847 if let Some(v) = style.max_w {
848 self.constraints.max_width = Some(v);
849 }
850 if let Some(v) = style.min_h {
851 self.constraints.min_height = Some(v);
852 }
853 if let Some(v) = style.max_h {
854 self.constraints.max_height = Some(v);
855 }
856 if let Some(v) = style.w_pct {
857 self.constraints.width_pct = Some(v);
858 }
859 if let Some(v) = style.h_pct {
860 self.constraints.height_pct = Some(v);
861 }
862 self
863 }
864
865 pub fn border(mut self, border: Border) -> Self {
867 self.border = Some(border);
868 self
869 }
870
871 pub fn border_top(mut self, show: bool) -> Self {
873 self.border_sides.top = show;
874 self
875 }
876
877 pub fn border_right(mut self, show: bool) -> Self {
879 self.border_sides.right = show;
880 self
881 }
882
883 pub fn border_bottom(mut self, show: bool) -> Self {
885 self.border_sides.bottom = show;
886 self
887 }
888
889 pub fn border_left(mut self, show: bool) -> Self {
891 self.border_sides.left = show;
892 self
893 }
894
895 pub fn border_sides(mut self, sides: BorderSides) -> Self {
897 self.border_sides = sides;
898 self
899 }
900
901 pub fn rounded(self) -> Self {
903 self.border(Border::Rounded)
904 }
905
906 pub fn border_style(mut self, style: Style) -> Self {
908 self.border_style = style;
909 self
910 }
911
912 pub fn dark_border_style(mut self, style: Style) -> Self {
914 self.dark_border_style = Some(style);
915 self
916 }
917
918 pub fn bg(mut self, color: Color) -> Self {
919 self.bg = Some(color);
920 self
921 }
922
923 pub fn dark_bg(mut self, color: Color) -> Self {
925 self.dark_bg = Some(color);
926 self
927 }
928
929 pub fn group_hover_bg(mut self, color: Color) -> Self {
931 self.group_hover_bg = Some(color);
932 self
933 }
934
935 pub fn group_hover_border_style(mut self, style: Style) -> Self {
937 self.group_hover_border_style = Some(style);
938 self
939 }
940
941 pub fn p(self, value: u32) -> Self {
945 self.pad(value)
946 }
947
948 pub fn pad(mut self, value: u32) -> Self {
950 self.padding = Padding::all(value);
951 self
952 }
953
954 pub fn px(mut self, value: u32) -> Self {
956 self.padding.left = value;
957 self.padding.right = value;
958 self
959 }
960
961 pub fn py(mut self, value: u32) -> Self {
963 self.padding.top = value;
964 self.padding.bottom = value;
965 self
966 }
967
968 pub fn pt(mut self, value: u32) -> Self {
970 self.padding.top = value;
971 self
972 }
973
974 pub fn pr(mut self, value: u32) -> Self {
976 self.padding.right = value;
977 self
978 }
979
980 pub fn pb(mut self, value: u32) -> Self {
982 self.padding.bottom = value;
983 self
984 }
985
986 pub fn pl(mut self, value: u32) -> Self {
988 self.padding.left = value;
989 self
990 }
991
992 pub fn padding(mut self, padding: Padding) -> Self {
994 self.padding = padding;
995 self
996 }
997
998 pub fn m(mut self, value: u32) -> Self {
1002 self.margin = Margin::all(value);
1003 self
1004 }
1005
1006 pub fn mx(mut self, value: u32) -> Self {
1008 self.margin.left = value;
1009 self.margin.right = value;
1010 self
1011 }
1012
1013 pub fn my(mut self, value: u32) -> Self {
1015 self.margin.top = value;
1016 self.margin.bottom = value;
1017 self
1018 }
1019
1020 pub fn mt(mut self, value: u32) -> Self {
1022 self.margin.top = value;
1023 self
1024 }
1025
1026 pub fn mr(mut self, value: u32) -> Self {
1028 self.margin.right = value;
1029 self
1030 }
1031
1032 pub fn mb(mut self, value: u32) -> Self {
1034 self.margin.bottom = value;
1035 self
1036 }
1037
1038 pub fn ml(mut self, value: u32) -> Self {
1040 self.margin.left = value;
1041 self
1042 }
1043
1044 pub fn margin(mut self, margin: Margin) -> Self {
1046 self.margin = margin;
1047 self
1048 }
1049
1050 pub fn w(mut self, value: u32) -> Self {
1054 self.constraints.min_width = Some(value);
1055 self.constraints.max_width = Some(value);
1056 self
1057 }
1058
1059 pub fn xs_w(self, value: u32) -> Self {
1066 let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1067 if is_xs {
1068 self.w(value)
1069 } else {
1070 self
1071 }
1072 }
1073
1074 pub fn sm_w(self, value: u32) -> Self {
1076 let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1077 if is_sm {
1078 self.w(value)
1079 } else {
1080 self
1081 }
1082 }
1083
1084 pub fn md_w(self, value: u32) -> Self {
1086 let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1087 if is_md {
1088 self.w(value)
1089 } else {
1090 self
1091 }
1092 }
1093
1094 pub fn lg_w(self, value: u32) -> Self {
1096 let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1097 if is_lg {
1098 self.w(value)
1099 } else {
1100 self
1101 }
1102 }
1103
1104 pub fn xl_w(self, value: u32) -> Self {
1106 let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1107 if is_xl {
1108 self.w(value)
1109 } else {
1110 self
1111 }
1112 }
1113 pub fn w_at(self, bp: Breakpoint, value: u32) -> Self {
1114 if self.ctx.breakpoint() == bp {
1115 self.w(value)
1116 } else {
1117 self
1118 }
1119 }
1120
1121 pub fn h(mut self, value: u32) -> Self {
1123 self.constraints.min_height = Some(value);
1124 self.constraints.max_height = Some(value);
1125 self
1126 }
1127
1128 pub fn xs_h(self, value: u32) -> Self {
1130 let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1131 if is_xs {
1132 self.h(value)
1133 } else {
1134 self
1135 }
1136 }
1137
1138 pub fn sm_h(self, value: u32) -> Self {
1140 let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1141 if is_sm {
1142 self.h(value)
1143 } else {
1144 self
1145 }
1146 }
1147
1148 pub fn md_h(self, value: u32) -> Self {
1150 let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1151 if is_md {
1152 self.h(value)
1153 } else {
1154 self
1155 }
1156 }
1157
1158 pub fn lg_h(self, value: u32) -> Self {
1160 let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1161 if is_lg {
1162 self.h(value)
1163 } else {
1164 self
1165 }
1166 }
1167
1168 pub fn xl_h(self, value: u32) -> Self {
1170 let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1171 if is_xl {
1172 self.h(value)
1173 } else {
1174 self
1175 }
1176 }
1177 pub fn h_at(self, bp: Breakpoint, value: u32) -> Self {
1178 if self.ctx.breakpoint() == bp {
1179 self.h(value)
1180 } else {
1181 self
1182 }
1183 }
1184
1185 pub fn min_w(mut self, value: u32) -> Self {
1187 self.constraints.min_width = Some(value);
1188 self
1189 }
1190
1191 pub fn xs_min_w(self, value: u32) -> Self {
1193 let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1194 if is_xs {
1195 self.min_w(value)
1196 } else {
1197 self
1198 }
1199 }
1200
1201 pub fn sm_min_w(self, value: u32) -> Self {
1203 let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1204 if is_sm {
1205 self.min_w(value)
1206 } else {
1207 self
1208 }
1209 }
1210
1211 pub fn md_min_w(self, value: u32) -> Self {
1213 let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1214 if is_md {
1215 self.min_w(value)
1216 } else {
1217 self
1218 }
1219 }
1220
1221 pub fn lg_min_w(self, value: u32) -> Self {
1223 let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1224 if is_lg {
1225 self.min_w(value)
1226 } else {
1227 self
1228 }
1229 }
1230
1231 pub fn xl_min_w(self, value: u32) -> Self {
1233 let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1234 if is_xl {
1235 self.min_w(value)
1236 } else {
1237 self
1238 }
1239 }
1240 pub fn min_w_at(self, bp: Breakpoint, value: u32) -> Self {
1241 if self.ctx.breakpoint() == bp {
1242 self.min_w(value)
1243 } else {
1244 self
1245 }
1246 }
1247
1248 pub fn max_w(mut self, value: u32) -> Self {
1250 self.constraints.max_width = Some(value);
1251 self
1252 }
1253
1254 pub fn xs_max_w(self, value: u32) -> Self {
1256 let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1257 if is_xs {
1258 self.max_w(value)
1259 } else {
1260 self
1261 }
1262 }
1263
1264 pub fn sm_max_w(self, value: u32) -> Self {
1266 let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1267 if is_sm {
1268 self.max_w(value)
1269 } else {
1270 self
1271 }
1272 }
1273
1274 pub fn md_max_w(self, value: u32) -> Self {
1276 let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1277 if is_md {
1278 self.max_w(value)
1279 } else {
1280 self
1281 }
1282 }
1283
1284 pub fn lg_max_w(self, value: u32) -> Self {
1286 let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1287 if is_lg {
1288 self.max_w(value)
1289 } else {
1290 self
1291 }
1292 }
1293
1294 pub fn xl_max_w(self, value: u32) -> Self {
1296 let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1297 if is_xl {
1298 self.max_w(value)
1299 } else {
1300 self
1301 }
1302 }
1303 pub fn max_w_at(self, bp: Breakpoint, value: u32) -> Self {
1304 if self.ctx.breakpoint() == bp {
1305 self.max_w(value)
1306 } else {
1307 self
1308 }
1309 }
1310
1311 pub fn min_h(mut self, value: u32) -> Self {
1313 self.constraints.min_height = Some(value);
1314 self
1315 }
1316
1317 pub fn max_h(mut self, value: u32) -> Self {
1319 self.constraints.max_height = Some(value);
1320 self
1321 }
1322
1323 pub fn min_width(mut self, value: u32) -> Self {
1325 self.constraints.min_width = Some(value);
1326 self
1327 }
1328
1329 pub fn max_width(mut self, value: u32) -> Self {
1331 self.constraints.max_width = Some(value);
1332 self
1333 }
1334
1335 pub fn min_height(mut self, value: u32) -> Self {
1337 self.constraints.min_height = Some(value);
1338 self
1339 }
1340
1341 pub fn max_height(mut self, value: u32) -> Self {
1343 self.constraints.max_height = Some(value);
1344 self
1345 }
1346
1347 pub fn w_pct(mut self, pct: u8) -> Self {
1349 self.constraints.width_pct = Some(pct.min(100));
1350 self
1351 }
1352
1353 pub fn h_pct(mut self, pct: u8) -> Self {
1355 self.constraints.height_pct = Some(pct.min(100));
1356 self
1357 }
1358
1359 pub fn constraints(mut self, constraints: Constraints) -> Self {
1361 self.constraints = constraints;
1362 self
1363 }
1364
1365 pub fn gap(mut self, gap: u32) -> Self {
1369 self.gap = gap;
1370 self
1371 }
1372
1373 pub fn xs_gap(self, value: u32) -> Self {
1375 let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1376 if is_xs {
1377 self.gap(value)
1378 } else {
1379 self
1380 }
1381 }
1382
1383 pub fn sm_gap(self, value: u32) -> Self {
1385 let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1386 if is_sm {
1387 self.gap(value)
1388 } else {
1389 self
1390 }
1391 }
1392
1393 pub fn md_gap(self, value: u32) -> Self {
1400 let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1401 if is_md {
1402 self.gap(value)
1403 } else {
1404 self
1405 }
1406 }
1407
1408 pub fn lg_gap(self, value: u32) -> Self {
1410 let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1411 if is_lg {
1412 self.gap(value)
1413 } else {
1414 self
1415 }
1416 }
1417
1418 pub fn xl_gap(self, value: u32) -> Self {
1420 let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1421 if is_xl {
1422 self.gap(value)
1423 } else {
1424 self
1425 }
1426 }
1427
1428 pub fn gap_at(self, bp: Breakpoint, value: u32) -> Self {
1429 if self.ctx.breakpoint() == bp {
1430 self.gap(value)
1431 } else {
1432 self
1433 }
1434 }
1435
1436 pub fn grow(mut self, grow: u16) -> Self {
1438 self.grow = grow;
1439 self
1440 }
1441
1442 pub fn xs_grow(self, value: u16) -> Self {
1444 let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1445 if is_xs {
1446 self.grow(value)
1447 } else {
1448 self
1449 }
1450 }
1451
1452 pub fn sm_grow(self, value: u16) -> Self {
1454 let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1455 if is_sm {
1456 self.grow(value)
1457 } else {
1458 self
1459 }
1460 }
1461
1462 pub fn md_grow(self, value: u16) -> Self {
1464 let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1465 if is_md {
1466 self.grow(value)
1467 } else {
1468 self
1469 }
1470 }
1471
1472 pub fn lg_grow(self, value: u16) -> Self {
1474 let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1475 if is_lg {
1476 self.grow(value)
1477 } else {
1478 self
1479 }
1480 }
1481
1482 pub fn xl_grow(self, value: u16) -> Self {
1484 let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1485 if is_xl {
1486 self.grow(value)
1487 } else {
1488 self
1489 }
1490 }
1491 pub fn grow_at(self, bp: Breakpoint, value: u16) -> Self {
1492 if self.ctx.breakpoint() == bp {
1493 self.grow(value)
1494 } else {
1495 self
1496 }
1497 }
1498
1499 pub fn xs_p(self, value: u32) -> Self {
1501 let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1502 if is_xs {
1503 self.p(value)
1504 } else {
1505 self
1506 }
1507 }
1508
1509 pub fn sm_p(self, value: u32) -> Self {
1511 let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1512 if is_sm {
1513 self.p(value)
1514 } else {
1515 self
1516 }
1517 }
1518
1519 pub fn md_p(self, value: u32) -> Self {
1521 let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1522 if is_md {
1523 self.p(value)
1524 } else {
1525 self
1526 }
1527 }
1528
1529 pub fn lg_p(self, value: u32) -> Self {
1531 let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1532 if is_lg {
1533 self.p(value)
1534 } else {
1535 self
1536 }
1537 }
1538
1539 pub fn xl_p(self, value: u32) -> Self {
1541 let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1542 if is_xl {
1543 self.p(value)
1544 } else {
1545 self
1546 }
1547 }
1548 pub fn p_at(self, bp: Breakpoint, value: u32) -> Self {
1549 if self.ctx.breakpoint() == bp {
1550 self.p(value)
1551 } else {
1552 self
1553 }
1554 }
1555
1556 pub fn align(mut self, align: Align) -> Self {
1560 self.align = align;
1561 self
1562 }
1563
1564 pub fn center(self) -> Self {
1566 self.align(Align::Center)
1567 }
1568
1569 pub fn justify(mut self, justify: Justify) -> Self {
1571 self.justify = justify;
1572 self
1573 }
1574
1575 pub fn space_between(self) -> Self {
1577 self.justify(Justify::SpaceBetween)
1578 }
1579
1580 pub fn space_around(self) -> Self {
1582 self.justify(Justify::SpaceAround)
1583 }
1584
1585 pub fn space_evenly(self) -> Self {
1587 self.justify(Justify::SpaceEvenly)
1588 }
1589
1590 pub fn title(self, title: impl Into<String>) -> Self {
1594 self.title_styled(title, Style::new())
1595 }
1596
1597 pub fn title_styled(mut self, title: impl Into<String>, style: Style) -> Self {
1599 self.title = Some((title.into(), style));
1600 self
1601 }
1602
1603 pub fn scroll_offset(mut self, offset: u32) -> Self {
1607 self.scroll_offset = Some(offset);
1608 self
1609 }
1610
1611 fn group_name(mut self, name: String) -> Self {
1612 self.group_name = Some(name);
1613 self
1614 }
1615
1616 pub fn col(self, f: impl FnOnce(&mut Context)) -> Response {
1621 self.finish(Direction::Column, f)
1622 }
1623
1624 pub fn row(self, f: impl FnOnce(&mut Context)) -> Response {
1629 self.finish(Direction::Row, f)
1630 }
1631
1632 pub fn line(mut self, f: impl FnOnce(&mut Context)) -> Response {
1637 self.gap = 0;
1638 self.finish(Direction::Row, f)
1639 }
1640
1641 pub fn draw(self, f: impl FnOnce(&mut crate::buffer::Buffer, Rect) + 'static) {
1656 let draw_id = self.ctx.deferred_draws.len();
1657 self.ctx.deferred_draws.push(Some(Box::new(f)));
1658 self.ctx.interaction_count += 1;
1659 self.ctx.commands.push(Command::RawDraw {
1660 draw_id,
1661 constraints: self.constraints,
1662 grow: self.grow,
1663 margin: self.margin,
1664 });
1665 }
1666
1667 fn finish(mut self, direction: Direction, f: impl FnOnce(&mut Context)) -> Response {
1668 let interaction_id = self.ctx.interaction_count;
1669 self.ctx.interaction_count += 1;
1670
1671 let in_hovered_group = self
1672 .group_name
1673 .as_ref()
1674 .map(|name| self.ctx.is_group_hovered(name))
1675 .unwrap_or(false)
1676 || self
1677 .ctx
1678 .group_stack
1679 .last()
1680 .map(|name| self.ctx.is_group_hovered(name))
1681 .unwrap_or(false);
1682 let in_focused_group = self
1683 .group_name
1684 .as_ref()
1685 .map(|name| self.ctx.is_group_focused(name))
1686 .unwrap_or(false)
1687 || self
1688 .ctx
1689 .group_stack
1690 .last()
1691 .map(|name| self.ctx.is_group_focused(name))
1692 .unwrap_or(false);
1693
1694 let resolved_bg = if self.ctx.dark_mode {
1695 self.dark_bg.or(self.bg)
1696 } else {
1697 self.bg
1698 };
1699 let resolved_border_style = if self.ctx.dark_mode {
1700 self.dark_border_style.unwrap_or(self.border_style)
1701 } else {
1702 self.border_style
1703 };
1704 let bg_color = if in_hovered_group || in_focused_group {
1705 self.group_hover_bg.or(resolved_bg)
1706 } else {
1707 resolved_bg
1708 };
1709 let border_style = if in_hovered_group || in_focused_group {
1710 self.group_hover_border_style
1711 .unwrap_or(resolved_border_style)
1712 } else {
1713 resolved_border_style
1714 };
1715 let group_name = self.group_name.take();
1716 let is_group_container = group_name.is_some();
1717
1718 if let Some(scroll_offset) = self.scroll_offset {
1719 self.ctx.commands.push(Command::BeginScrollable {
1720 grow: self.grow,
1721 border: self.border,
1722 border_sides: self.border_sides,
1723 border_style,
1724 padding: self.padding,
1725 margin: self.margin,
1726 constraints: self.constraints,
1727 title: self.title,
1728 scroll_offset,
1729 });
1730 } else {
1731 self.ctx.commands.push(Command::BeginContainer {
1732 direction,
1733 gap: self.gap,
1734 align: self.align,
1735 justify: self.justify,
1736 border: self.border,
1737 border_sides: self.border_sides,
1738 border_style,
1739 bg_color,
1740 padding: self.padding,
1741 margin: self.margin,
1742 constraints: self.constraints,
1743 title: self.title,
1744 grow: self.grow,
1745 group_name,
1746 });
1747 }
1748 f(self.ctx);
1749 self.ctx.commands.push(Command::EndContainer);
1750 self.ctx.last_text_idx = None;
1751
1752 if is_group_container {
1753 self.ctx.group_stack.pop();
1754 self.ctx.group_count = self.ctx.group_count.saturating_sub(1);
1755 }
1756
1757 self.ctx.response_for(interaction_id)
1758 }
1759}
1760
1761impl Context {
1762 pub(crate) fn new(
1763 events: Vec<Event>,
1764 width: u32,
1765 height: u32,
1766 state: &mut FrameState,
1767 theme: Theme,
1768 ) -> Self {
1769 let consumed = vec![false; events.len()];
1770
1771 let mut mouse_pos = state.last_mouse_pos;
1772 let mut click_pos = None;
1773 for event in &events {
1774 if let Event::Mouse(mouse) = event {
1775 mouse_pos = Some((mouse.x, mouse.y));
1776 if matches!(mouse.kind, MouseKind::Down(MouseButton::Left)) {
1777 click_pos = Some((mouse.x, mouse.y));
1778 }
1779 }
1780 }
1781
1782 let mut focus_index = state.focus_index;
1783 if let Some((mx, my)) = click_pos {
1784 let mut best: Option<(usize, u64)> = None;
1785 for &(fid, rect) in &state.prev_focus_rects {
1786 if mx >= rect.x && mx < rect.right() && my >= rect.y && my < rect.bottom() {
1787 let area = rect.width as u64 * rect.height as u64;
1788 if best.map_or(true, |(_, ba)| area < ba) {
1789 best = Some((fid, area));
1790 }
1791 }
1792 }
1793 if let Some((fid, _)) = best {
1794 focus_index = fid;
1795 }
1796 }
1797
1798 Self {
1799 commands: Vec::new(),
1800 events,
1801 consumed,
1802 should_quit: false,
1803 area_width: width,
1804 area_height: height,
1805 tick: state.tick,
1806 focus_index,
1807 focus_count: 0,
1808 hook_states: std::mem::take(&mut state.hook_states),
1809 hook_cursor: 0,
1810 prev_focus_count: state.prev_focus_count,
1811 scroll_count: 0,
1812 prev_scroll_infos: std::mem::take(&mut state.prev_scroll_infos),
1813 prev_scroll_rects: std::mem::take(&mut state.prev_scroll_rects),
1814 interaction_count: 0,
1815 prev_hit_map: std::mem::take(&mut state.prev_hit_map),
1816 group_stack: Vec::new(),
1817 prev_group_rects: std::mem::take(&mut state.prev_group_rects),
1818 group_count: 0,
1819 prev_focus_groups: std::mem::take(&mut state.prev_focus_groups),
1820 _prev_focus_rects: std::mem::take(&mut state.prev_focus_rects),
1821 mouse_pos,
1822 click_pos,
1823 last_text_idx: None,
1824 overlay_depth: 0,
1825 modal_active: false,
1826 prev_modal_active: state.prev_modal_active,
1827 clipboard_text: None,
1828 debug: state.debug_mode,
1829 theme,
1830 dark_mode: theme.is_dark,
1831 is_real_terminal: false,
1832 deferred_draws: Vec::new(),
1833 }
1834 }
1835
1836 pub(crate) fn process_focus_keys(&mut self) {
1837 for (i, event) in self.events.iter().enumerate() {
1838 if let Event::Key(key) = event {
1839 if key.kind != KeyEventKind::Press {
1840 continue;
1841 }
1842 if key.code == KeyCode::Tab && !key.modifiers.contains(KeyModifiers::SHIFT) {
1843 if self.prev_focus_count > 0 {
1844 self.focus_index = (self.focus_index + 1) % self.prev_focus_count;
1845 }
1846 self.consumed[i] = true;
1847 } else if (key.code == KeyCode::Tab && key.modifiers.contains(KeyModifiers::SHIFT))
1848 || key.code == KeyCode::BackTab
1849 {
1850 if self.prev_focus_count > 0 {
1851 self.focus_index = if self.focus_index == 0 {
1852 self.prev_focus_count - 1
1853 } else {
1854 self.focus_index - 1
1855 };
1856 }
1857 self.consumed[i] = true;
1858 }
1859 }
1860 }
1861 }
1862
1863 pub fn widget<W: Widget>(&mut self, w: &mut W) -> W::Response {
1867 w.ui(self)
1868 }
1869
1870 pub fn error_boundary(&mut self, f: impl FnOnce(&mut Context)) {
1885 self.error_boundary_with(f, |ui, msg| {
1886 ui.styled(
1887 format!("⚠ Error: {msg}"),
1888 Style::new().fg(ui.theme.error).bold(),
1889 );
1890 });
1891 }
1892
1893 pub fn error_boundary_with(
1913 &mut self,
1914 f: impl FnOnce(&mut Context),
1915 fallback: impl FnOnce(&mut Context, String),
1916 ) {
1917 let snapshot = ContextSnapshot::capture(self);
1918
1919 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1920 f(self);
1921 }));
1922
1923 match result {
1924 Ok(()) => {}
1925 Err(panic_info) => {
1926 if self.is_real_terminal {
1927 let _ = crossterm::terminal::enable_raw_mode();
1928 let _ = crossterm::execute!(
1929 std::io::stdout(),
1930 crossterm::terminal::EnterAlternateScreen
1931 );
1932 }
1933
1934 snapshot.restore(self);
1935
1936 let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
1937 (*s).to_string()
1938 } else if let Some(s) = panic_info.downcast_ref::<String>() {
1939 s.clone()
1940 } else {
1941 "widget panicked".to_string()
1942 };
1943
1944 fallback(self, msg);
1945 }
1946 }
1947 }
1948
1949 pub fn interaction(&mut self) -> Response {
1955 if (self.modal_active || self.prev_modal_active) && self.overlay_depth == 0 {
1956 return Response::default();
1957 }
1958 let id = self.interaction_count;
1959 self.interaction_count += 1;
1960 self.response_for(id)
1961 }
1962
1963 pub fn register_focusable(&mut self) -> bool {
1968 if (self.modal_active || self.prev_modal_active) && self.overlay_depth == 0 {
1969 return false;
1970 }
1971 let id = self.focus_count;
1972 self.focus_count += 1;
1973 self.commands.push(Command::FocusMarker(id));
1974 if self.prev_focus_count == 0 {
1975 return true;
1976 }
1977 self.focus_index % self.prev_focus_count == id
1978 }
1979
1980 pub fn use_state<T: 'static>(&mut self, init: impl FnOnce() -> T) -> State<T> {
1998 let idx = self.hook_cursor;
1999 self.hook_cursor += 1;
2000
2001 if idx >= self.hook_states.len() {
2002 self.hook_states.push(Box::new(init()));
2003 }
2004
2005 State {
2006 idx,
2007 _marker: std::marker::PhantomData,
2008 }
2009 }
2010
2011 pub fn use_memo<T: 'static, D: PartialEq + Clone + 'static>(
2019 &mut self,
2020 deps: &D,
2021 compute: impl FnOnce(&D) -> T,
2022 ) -> &T {
2023 let idx = self.hook_cursor;
2024 self.hook_cursor += 1;
2025
2026 let should_recompute = if idx >= self.hook_states.len() {
2027 true
2028 } else {
2029 let (stored_deps, _) = self.hook_states[idx]
2030 .downcast_ref::<(D, T)>()
2031 .expect("use_memo type mismatch");
2032 stored_deps != deps
2033 };
2034
2035 if should_recompute {
2036 let value = compute(deps);
2037 let slot = Box::new((deps.clone(), value));
2038 if idx < self.hook_states.len() {
2039 self.hook_states[idx] = slot;
2040 } else {
2041 self.hook_states.push(slot);
2042 }
2043 }
2044
2045 let (_, value) = self.hook_states[idx]
2046 .downcast_ref::<(D, T)>()
2047 .expect("use_memo type mismatch");
2048 value
2049 }
2050}
2051
2052mod widgets_display;
2053mod widgets_input;
2054mod widgets_interactive;
2055mod widgets_viz;
2056
2057#[inline]
2058fn byte_index_for_char(value: &str, char_index: usize) -> usize {
2059 if char_index == 0 {
2060 return 0;
2061 }
2062 value
2063 .char_indices()
2064 .nth(char_index)
2065 .map_or(value.len(), |(idx, _)| idx)
2066}
2067
2068fn format_token_count(count: usize) -> String {
2069 if count >= 1_000_000 {
2070 format!("{:.1}M", count as f64 / 1_000_000.0)
2071 } else if count >= 1_000 {
2072 format!("{:.1}k", count as f64 / 1_000.0)
2073 } else {
2074 format!("{count}")
2075 }
2076}
2077
2078fn format_table_row(cells: &[String], widths: &[u32], separator: &str) -> String {
2079 let mut parts: Vec<String> = Vec::new();
2080 for (i, width) in widths.iter().enumerate() {
2081 let cell = cells.get(i).map(String::as_str).unwrap_or("");
2082 let cell_width = UnicodeWidthStr::width(cell) as u32;
2083 let padding = (*width).saturating_sub(cell_width) as usize;
2084 parts.push(format!("{cell}{}", " ".repeat(padding)));
2085 }
2086 parts.join(separator)
2087}
2088
2089fn format_compact_number(value: f64) -> String {
2090 if value.fract().abs() < f64::EPSILON {
2091 return format!("{value:.0}");
2092 }
2093
2094 let mut s = format!("{value:.2}");
2095 while s.contains('.') && s.ends_with('0') {
2096 s.pop();
2097 }
2098 if s.ends_with('.') {
2099 s.pop();
2100 }
2101 s
2102}
2103
2104fn center_text(text: &str, width: usize) -> String {
2105 let text_width = UnicodeWidthStr::width(text);
2106 if text_width >= width {
2107 return text.to_string();
2108 }
2109
2110 let total = width - text_width;
2111 let left = total / 2;
2112 let right = total - left;
2113 format!("{}{}{}", " ".repeat(left), text, " ".repeat(right))
2114}
2115
2116struct TextareaVLine {
2117 logical_row: usize,
2118 char_start: usize,
2119 char_count: usize,
2120}
2121
2122fn textarea_build_visual_lines(lines: &[String], wrap_width: u32) -> Vec<TextareaVLine> {
2123 let mut out = Vec::new();
2124 for (row, line) in lines.iter().enumerate() {
2125 if line.is_empty() || wrap_width == u32::MAX {
2126 out.push(TextareaVLine {
2127 logical_row: row,
2128 char_start: 0,
2129 char_count: line.chars().count(),
2130 });
2131 continue;
2132 }
2133 let mut seg_start = 0usize;
2134 let mut seg_chars = 0usize;
2135 let mut seg_width = 0u32;
2136 for (idx, ch) in line.chars().enumerate() {
2137 let cw = UnicodeWidthChar::width(ch).unwrap_or(0) as u32;
2138 if seg_width + cw > wrap_width && seg_chars > 0 {
2139 out.push(TextareaVLine {
2140 logical_row: row,
2141 char_start: seg_start,
2142 char_count: seg_chars,
2143 });
2144 seg_start = idx;
2145 seg_chars = 0;
2146 seg_width = 0;
2147 }
2148 seg_chars += 1;
2149 seg_width += cw;
2150 }
2151 out.push(TextareaVLine {
2152 logical_row: row,
2153 char_start: seg_start,
2154 char_count: seg_chars,
2155 });
2156 }
2157 out
2158}
2159
2160fn textarea_logical_to_visual(
2161 vlines: &[TextareaVLine],
2162 logical_row: usize,
2163 logical_col: usize,
2164) -> (usize, usize) {
2165 for (i, vl) in vlines.iter().enumerate() {
2166 if vl.logical_row != logical_row {
2167 continue;
2168 }
2169 let seg_end = vl.char_start + vl.char_count;
2170 if logical_col >= vl.char_start && logical_col < seg_end {
2171 return (i, logical_col - vl.char_start);
2172 }
2173 if logical_col == seg_end {
2174 let is_last_seg = vlines
2175 .get(i + 1)
2176 .map_or(true, |next| next.logical_row != logical_row);
2177 if is_last_seg {
2178 return (i, logical_col - vl.char_start);
2179 }
2180 }
2181 }
2182 (vlines.len().saturating_sub(1), 0)
2183}
2184
2185fn textarea_visual_to_logical(
2186 vlines: &[TextareaVLine],
2187 visual_row: usize,
2188 visual_col: usize,
2189) -> (usize, usize) {
2190 if let Some(vl) = vlines.get(visual_row) {
2191 let logical_col = vl.char_start + visual_col.min(vl.char_count);
2192 (vl.logical_row, logical_col)
2193 } else {
2194 (0, 0)
2195 }
2196}
2197
2198fn open_url(url: &str) -> std::io::Result<()> {
2199 #[cfg(target_os = "macos")]
2200 {
2201 std::process::Command::new("open").arg(url).spawn()?;
2202 }
2203 #[cfg(target_os = "linux")]
2204 {
2205 std::process::Command::new("xdg-open").arg(url).spawn()?;
2206 }
2207 #[cfg(target_os = "windows")]
2208 {
2209 std::process::Command::new("cmd")
2210 .args(["/c", "start", "", url])
2211 .spawn()?;
2212 }
2213 Ok(())
2214}