1use std::arch::breakpoint;
147#[cfg(debug_assertions)]
148use std::collections::HashSet;
149use std::fmt::Write as _;
150use std::{iter, mem, ptr, time};
151
152use crate::arena::{Arena, ArenaString, scratch_arena};
153use crate::buffer::{CursorMovement, RcTextBuffer, TextBuffer, TextBufferCell};
154use crate::cell::*;
155use crate::clipboard::Clipboard;
156use crate::document::WriteableDocument;
157use crate::framebuffer::{Attributes, Framebuffer, INDEXED_COLORS_COUNT, IndexedColor};
158use crate::hash::*;
159use crate::helpers::*;
160use crate::input::{InputKeyMod, kbmod, vk};
161use crate::{apperr, arena_format, input, simd, unicode};
162
163const ROOT_ID: u64 = 0x14057B7EF767814F; const SHIFT_TAB: InputKey = vk::TAB.with_modifiers(kbmod::SHIFT);
165const KBMOD_FOR_WORD_NAV: InputKeyMod =
166 if cfg!(target_os = "macos") { kbmod::ALT } else { kbmod::CTRL };
167
168type Input<'input> = input::Input<'input>;
169type InputKey = input::InputKey;
170type InputMouseState = input::InputMouseState;
171
172struct CachedTextBuffer {
176 node_id: u64,
177 editor: RcTextBuffer,
178 seen: bool,
179}
180
181enum TextBufferPayload<'a> {
184 Editline(&'a mut dyn WriteableDocument),
185 Textarea(RcTextBuffer),
186}
187
188pub struct ModifierTranslations {
191 pub ctrl: &'static str,
192 pub alt: &'static str,
193 pub shift: &'static str,
194}
195
196#[derive(Default, Clone, Copy, PartialEq, Eq)]
198pub enum Anchor {
199 #[default]
201 Last,
202 Parent,
204 Root,
206}
207
208#[derive(Default)]
210pub struct FloatSpec {
211 pub anchor: Anchor,
213 pub gravity_x: f32,
215 pub gravity_y: f32,
216 pub offset_x: f32,
218 pub offset_y: f32,
219}
220
221#[derive(Clone, Copy, PartialEq, Eq)]
223pub enum ListSelection {
224 Unchanged,
226 Selected,
228 Activated,
231}
232
233#[derive(Default)]
235pub enum Position {
236 #[default]
238 Stretch,
239 Left,
241 Center,
243 Right,
245}
246
247#[derive(Default, Clone, Copy, PartialEq, Eq)]
250pub enum Overflow {
251 #[default]
253 Clip,
254 TruncateHead,
256 TruncateMiddle,
258 TruncateTail,
260}
261
262#[derive(Clone, Copy)]
264pub struct ButtonStyle {
265 accelerator: Option<char>,
266 checked: Option<bool>,
267 bracketed: bool,
268}
269
270impl ButtonStyle {
271 pub fn accelerator(self, char: char) -> Self {
275 Self { accelerator: Some(char), ..self }
276 }
277 pub fn checked(self, checked: bool) -> Self {
279 Self { checked: Some(checked), ..self }
280 }
281 pub fn bracketed(self, bracketed: bool) -> Self {
283 Self { bracketed, ..self }
284 }
285}
286
287impl Default for ButtonStyle {
288 fn default() -> Self {
289 Self {
290 accelerator: None,
291 checked: None,
292 bracketed: true, }
294 }
295}
296
297pub struct Tui {
304 arena_prev: Arena,
306 arena_next: Arena,
308 prev_tree: Tree<'static>,
311 prev_node_map: NodeMap<'static>,
314 framebuffer: Framebuffer,
316
317 modifier_translations: ModifierTranslations,
318 floater_default_bg: u32,
319 floater_default_fg: u32,
320 modal_default_bg: u32,
321 modal_default_fg: u32,
322
323 size: Size,
329 mouse_position: Point,
331 mouse_down_position: Point,
334 left_mouse_down_target: u64,
337 mouse_up_timestamp: std::time::Instant,
340 mouse_state: InputMouseState,
342 mouse_is_drag: bool,
344 mouse_click_counter: CoordType,
347 mouse_down_node_path: Vec<u64>,
349 first_click_position: Point,
351 first_click_target: u64,
354
355 focused_node_path: Vec<u64>,
357 focused_node_for_scrolling: u64,
361
362 cached_text_buffers: Vec<CachedTextBuffer>,
364
365 clipboard: Clipboard,
367
368 settling_have: i32,
369 settling_want: i32,
370 read_timeout: time::Duration,
371}
372
373impl Tui {
374 pub fn new() -> apperr::Result<Self> {
376 let arena_prev = Arena::new(128 * MEBI)?;
377 let arena_next = Arena::new(128 * MEBI)?;
378 let prev_tree = Tree::new(unsafe { mem::transmute::<&Arena, &Arena>(&arena_next) });
381
382 let mut tui = Self {
383 arena_prev,
384 arena_next,
385 prev_tree,
386 prev_node_map: Default::default(),
387 framebuffer: Framebuffer::new(),
388
389 modifier_translations: ModifierTranslations {
390 ctrl: "Ctrl",
391 alt: "Alt",
392 shift: "Shift",
393 },
394 floater_default_bg: 0,
395 floater_default_fg: 0,
396 modal_default_bg: 0,
397 modal_default_fg: 0,
398
399 size: Size { width: 0, height: 0 },
400 mouse_position: Point::MIN,
401 mouse_down_position: Point::MIN,
402 left_mouse_down_target: 0,
403 mouse_up_timestamp: std::time::Instant::now(),
404 mouse_state: InputMouseState::None,
405 mouse_is_drag: false,
406 mouse_click_counter: 0,
407 mouse_down_node_path: Vec::with_capacity(16),
408 first_click_position: Point::MIN,
409 first_click_target: 0,
410
411 focused_node_path: Vec::with_capacity(16),
412 focused_node_for_scrolling: ROOT_ID,
413
414 cached_text_buffers: Vec::with_capacity(16),
415
416 clipboard: Default::default(),
417
418 settling_have: 0,
419 settling_want: 0,
420 read_timeout: time::Duration::MAX,
421 };
422 Self::clean_node_path(&mut tui.mouse_down_node_path);
423 Self::clean_node_path(&mut tui.focused_node_path);
424 Ok(tui)
425 }
426
427 pub fn setup_indexed_colors(&mut self, colors: [u32; INDEXED_COLORS_COUNT]) {
429 self.framebuffer.set_indexed_colors(colors);
430 }
431
432 pub fn setup_modifier_translations(&mut self, translations: ModifierTranslations) {
434 self.modifier_translations = translations;
435 }
436
437 pub fn set_floater_default_bg(&mut self, color: u32) {
439 self.floater_default_bg = color;
440 }
441
442 pub fn set_floater_default_fg(&mut self, color: u32) {
444 self.floater_default_fg = color;
445 }
446
447 pub fn set_modal_default_bg(&mut self, color: u32) {
449 self.modal_default_bg = color;
450 }
451
452 pub fn set_modal_default_fg(&mut self, color: u32) {
454 self.modal_default_fg = color;
455 }
456
457 pub fn read_timeout(&mut self) -> time::Duration {
460 mem::replace(&mut self.read_timeout, time::Duration::MAX)
461 }
462
463 pub fn size(&self) -> Size {
465 self.size
468 }
469
470 #[inline]
472 pub fn indexed(&self, index: IndexedColor) -> u32 {
473 self.framebuffer.indexed(index)
474 }
475
476 #[inline]
479 pub fn indexed_alpha(&self, index: IndexedColor, numerator: u32, denominator: u32) -> u32 {
480 self.framebuffer.indexed_alpha(index, numerator, denominator)
481 }
482
483 pub fn contrasted(&self, color: u32) -> u32 {
486 self.framebuffer.contrasted(color)
487 }
488
489 pub fn clipboard_ref(&self) -> &Clipboard {
491 &self.clipboard
492 }
493
494 pub fn clipboard_mut(&mut self) -> &mut Clipboard {
496 &mut self.clipboard
497 }
498
499 pub fn create_context<'a, 'input>(
501 &'a mut self,
502 input: Option<Input<'input>>,
503 ) -> Context<'a, 'input> {
504 mem::swap(&mut self.arena_prev, &mut self.arena_next);
507 unsafe { self.arena_next.reset(0) };
508
509 if self.mouse_state > InputMouseState::Right {
513 self.mouse_down_position = Point::MIN;
514 self.mouse_down_node_path.clear();
515 self.left_mouse_down_target = 0;
516 self.mouse_state = InputMouseState::None;
517 self.mouse_is_drag = false;
518 }
519
520 let now = std::time::Instant::now();
521 let mut input_text = None;
522 let mut input_keyboard = None;
523 let mut input_mouse_modifiers = kbmod::NONE;
524 let mut input_mouse_click = 0;
525 let mut input_scroll_delta = Point { x: 0, y: 0 };
526 let input_consumed = self.needs_settling() && input.is_none();
531
532 if self.scroll_to_focused() {
533 self.needs_more_settling();
534 }
535
536 match input {
537 None => {}
538 Some(Input::Resize(resize)) => {
539 assert!(resize.width > 0 && resize.height > 0);
540 assert!(resize.width < 32768 && resize.height < 32768);
541 self.size = resize;
542 }
543 Some(Input::Text(text)) => {
544 input_text = Some(text);
545 if text.len() == 1 {
551 let ch = text.as_bytes()[0];
552 input_keyboard = InputKey::from_ascii(ch as char)
553 }
554 }
555 Some(Input::Paste(paste)) => {
556 let clipboard = self.clipboard_mut();
557 clipboard.write(paste);
558 clipboard.mark_as_synchronized();
559 input_keyboard = Some(kbmod::CTRL | vk::V);
560 }
561 Some(Input::Keyboard(keyboard)) => {
562 input_keyboard = Some(keyboard);
563 }
564 Some(Input::Mouse(mouse)) => {
565 let mut next_state = mouse.state;
566 let next_position = mouse.position;
567 let next_scroll = mouse.scroll;
568 let mouse_down = self.mouse_state == InputMouseState::None
569 && next_state != InputMouseState::None;
570 let mouse_up = self.mouse_state != InputMouseState::None
571 && next_state == InputMouseState::None;
572 let is_drag = self.mouse_state == InputMouseState::Left
573 && next_state == InputMouseState::Left
574 && next_position != self.mouse_position;
575
576 let mut hovered_node = None; let mut focused_node = None; if mouse_down || mouse_up {
579 for root in self.prev_tree.iterate_roots_rev() {
582 Tree::visit_all(root, root, true, |node| {
584 let n = node.borrow();
585 if !n.outer_clipped.contains(next_position) {
586 return VisitControl::SkipChildren;
588 }
589 hovered_node = Some(node);
590 if n.attributes.focusable {
591 focused_node = Some(node);
592 }
593 VisitControl::Continue
594 });
595
596 if hovered_node.is_some() {
599 break;
600 }
601
602 if matches!(root.borrow().content, NodeContent::Modal(_)) {
605 break;
606 }
607 }
608 }
609
610 if mouse_down {
611 Self::build_node_path(hovered_node, &mut self.mouse_down_node_path);
613
614 let mut target = 0;
616 if next_state == InputMouseState::Left {
617 target = focused_node.map_or(0, |n| n.borrow().id);
618 Self::build_node_path(focused_node, &mut self.focused_node_path);
619 self.needs_more_settling(); }
621
622 if self.mouse_click_counter != 0 {
625 if self.first_click_target != target
626 || self.first_click_position != next_position
627 || (now - self.mouse_up_timestamp)
628 > std::time::Duration::from_millis(500)
629 {
630 self.mouse_click_counter = 0;
633 self.first_click_position = Point::MIN;
634 self.first_click_target = 0;
635 } else {
636 self.mouse_click_counter += 1;
637 input_mouse_click = self.mouse_click_counter;
638 };
639 }
640
641 self.left_mouse_down_target = target;
643 self.mouse_down_position = next_position;
644 } else if mouse_up {
645 next_state = InputMouseState::Release;
647
648 let target = focused_node.map_or(0, |n| n.borrow().id);
649
650 if self.left_mouse_down_target == 0 || self.left_mouse_down_target != target {
651 self.mouse_click_counter = 0;
654 self.first_click_position = Point::MIN;
655 self.first_click_target = 0;
656 } else if self.mouse_click_counter == 0 {
657 self.mouse_click_counter = 1;
659 self.first_click_position = self.mouse_down_position;
660 self.first_click_target = target;
661 input_mouse_click = 1;
662 }
663
664 self.mouse_up_timestamp = now;
665 } else if is_drag {
666 self.mouse_is_drag = true;
667 }
668
669 input_mouse_modifiers = mouse.modifiers;
670 input_scroll_delta = next_scroll;
671 self.mouse_position = next_position;
672 self.mouse_state = next_state;
673 }
674 }
675
676 if !input_consumed {
677 self.settling_have = 0;
679 self.settling_want = 1;
680 }
681
682 let tree = Tree::new(unsafe { mem::transmute::<&Arena, &Arena>(&self.arena_next) });
686
687 Context {
688 tui: self,
689
690 input_text,
691 input_keyboard,
692 input_mouse_modifiers,
693 input_mouse_click,
694 input_scroll_delta,
695 input_consumed,
696
697 tree,
698 last_modal: None,
699 focused_node: None,
700 next_block_id_mixin: 0,
701 needs_settling: false,
702
703 #[cfg(debug_assertions)]
704 seen_ids: HashSet::new(),
705 }
706 }
707
708 fn report_context_completion<'a>(&'a mut self, ctx: &mut Context<'a, '_>) {
709 debug_assert!(
712 ctx.tree.current_node.borrow().stack_parent.is_none(),
713 "Dangling parent! Did you miss a block_end?"
714 );
715
716 ctx.block_end();
718
719 if let Some(node) = ctx.last_modal
721 && !self.is_subtree_focused(&node.borrow())
722 {
723 ctx.steal_focus_for(node);
724 }
725
726 let mut needs_settling = ctx.needs_settling;
729 needs_settling |= self.prev_tree.checksum != ctx.tree.checksum;
730
731 unsafe {
737 self.prev_tree = mem::transmute_copy(&ctx.tree);
738 self.prev_node_map = NodeMap::new(mem::transmute(&self.arena_next), &self.prev_tree);
739 }
740
741 let mut focus_path_pop_min = 0;
742 if !ctx.input_consumed && ctx.consume_shortcut(vk::ESCAPE) {
744 focus_path_pop_min = 1;
745 }
746
747 needs_settling |= self.pop_focusable_node(focus_path_pop_min);
751
752 self.settling_have += 1;
755
756 if needs_settling {
757 self.needs_more_settling();
758 }
759
760 self.cached_text_buffers.retain(|c| c.seen);
762
763 for root in Tree::iterate_siblings(Some(self.prev_tree.root_first)) {
764 let mut root = root.borrow_mut();
765 root.compute_intrinsic_size();
766 }
767
768 let viewport = self.size.as_rect();
769
770 for root in Tree::iterate_siblings(Some(self.prev_tree.root_first)) {
771 let mut root = root.borrow_mut();
772 let root = &mut *root;
773
774 if let Some(float) = &root.attributes.float {
775 let mut x = 0;
776 let mut y = 0;
777
778 if let Some(node) = root.parent {
779 let node = node.borrow();
780 x = node.outer.left;
781 y = node.outer.top;
782 }
783
784 let size = root.intrinsic_to_outer();
785
786 x += (float.offset_x - float.gravity_x * size.width as f32) as CoordType;
787 y += (float.offset_y - float.gravity_y * size.height as f32) as CoordType;
788
789 root.outer.left = x;
790 root.outer.top = y;
791 root.outer.right = x + size.width;
792 root.outer.bottom = y + size.height;
793 root.outer = root.outer.intersect(viewport);
794 } else {
795 root.outer = viewport;
796 }
797
798 root.inner = root.outer_to_inner(root.outer);
799 root.outer_clipped = root.outer;
800 root.inner_clipped = root.inner;
801
802 let outer = root.outer;
803 root.layout_children(outer);
804 }
805 }
806
807 fn build_node_path(node: Option<&NodeCell>, path: &mut Vec<u64>) {
808 path.clear();
809 if let Some(mut node) = node {
810 loop {
811 let n = node.borrow();
812 path.push(n.id);
813 node = match n.parent {
814 Some(parent) => parent,
815 None => break,
816 };
817 }
818 path.reverse();
819 } else {
820 path.push(ROOT_ID);
821 }
822 }
823
824 fn clean_node_path(path: &mut Vec<u64>) {
825 Self::build_node_path(None, path);
826 }
827
828 pub fn needs_settling(&mut self) -> bool {
830 self.settling_have <= self.settling_want
831 }
832
833 fn needs_more_settling(&mut self) {
834 if cfg!(debug_assertions) && self.settling_have == 15 {
838 breakpoint();
839 }
840 self.settling_want = (self.settling_have + 1).min(20);
841 }
842
843 pub fn render<'a>(&mut self, arena: &'a Arena) -> ArenaString<'a> {
845 self.framebuffer.flip(self.size);
846 for child in self.prev_tree.iterate_roots() {
847 let mut child = child.borrow_mut();
848 self.render_node(&mut child);
849 }
850 self.framebuffer.render(arena)
851 }
852
853 #[allow(clippy::only_used_in_recursion)]
855 fn render_node(&mut self, node: &mut Node) {
856 let outer_clipped = node.outer_clipped;
857 if outer_clipped.is_empty() {
858 return;
859 }
860
861 let scratch = scratch_arena(None);
862
863 if node.attributes.bordered {
864 {
866 let mut fill = ArenaString::new_in(&scratch);
867 fill.push('┌');
868 fill.push_repeat('─', (outer_clipped.right - outer_clipped.left - 2) as usize);
869 fill.push('┐');
870 self.framebuffer.replace_text(
871 outer_clipped.top,
872 outer_clipped.left,
873 outer_clipped.right,
874 &fill,
875 );
876 }
877
878 {
880 let mut fill = ArenaString::new_in(&scratch);
881 fill.push('│');
882 fill.push_repeat(' ', (outer_clipped.right - outer_clipped.left - 2) as usize);
883 fill.push('│');
884
885 for y in outer_clipped.top + 1..outer_clipped.bottom - 1 {
886 self.framebuffer.replace_text(
887 y,
888 outer_clipped.left,
889 outer_clipped.right,
890 &fill,
891 );
892 }
893 }
894
895 {
897 let mut fill = ArenaString::new_in(&scratch);
898 fill.push('└');
899 fill.push_repeat('─', (outer_clipped.right - outer_clipped.left - 2) as usize);
900 fill.push('┘');
901 self.framebuffer.replace_text(
902 outer_clipped.bottom - 1,
903 outer_clipped.left,
904 outer_clipped.right,
905 &fill,
906 );
907 }
908 }
909
910 if node.attributes.float.is_some() {
911 if !node.attributes.bordered {
912 let mut fill = ArenaString::new_in(&scratch);
913 fill.push_repeat(' ', (outer_clipped.right - outer_clipped.left) as usize);
914
915 for y in outer_clipped.top..outer_clipped.bottom {
916 self.framebuffer.replace_text(
917 y,
918 outer_clipped.left,
919 outer_clipped.right,
920 &fill,
921 );
922 }
923 }
924
925 self.framebuffer.replace_attr(outer_clipped, Attributes::All, Attributes::None);
926
927 if matches!(node.content, NodeContent::Modal(_)) {
928 let rect =
929 Rect { left: 0, top: 0, right: self.size.width, bottom: self.size.height };
930 let dim = self.indexed_alpha(IndexedColor::Background, 1, 2);
931 self.framebuffer.blend_bg(rect, dim);
932 self.framebuffer.blend_fg(rect, dim);
933 }
934 }
935
936 self.framebuffer.blend_bg(outer_clipped, node.attributes.bg);
937 self.framebuffer.blend_fg(outer_clipped, node.attributes.fg);
938
939 if node.attributes.reverse {
940 self.framebuffer.reverse(outer_clipped);
941 }
942
943 let inner = node.inner;
944 let inner_clipped = node.inner_clipped;
945 if inner_clipped.is_empty() {
946 return;
947 }
948
949 match &mut node.content {
950 NodeContent::Modal(title) => {
951 if !title.is_empty() {
952 self.framebuffer.replace_text(
953 node.outer.top,
954 node.outer.left + 2,
955 node.outer.right - 1,
956 title,
957 );
958 }
959 }
960 NodeContent::Text(content) => self.render_styled_text(
961 inner,
962 node.intrinsic_size.width,
963 &content.text,
964 &content.chunks,
965 content.overflow,
966 ),
967 NodeContent::Textarea(tc) => {
968 let mut tb = tc.buffer.borrow_mut();
969 let mut destination = Rect {
970 left: inner_clipped.left,
971 top: inner_clipped.top,
972 right: inner_clipped.right,
973 bottom: inner_clipped.bottom,
974 };
975
976 if !tc.single_line {
977 destination.right -= 1;
979 }
980
981 if let Some(res) =
982 tb.render(tc.scroll_offset, destination, tc.has_focus, &mut self.framebuffer)
983 {
984 tc.scroll_offset_x_max = res.visual_pos_x_max;
985 }
986
987 if !tc.single_line {
988 let track = Rect {
990 left: inner_clipped.right - 1,
991 top: inner_clipped.top,
992 right: inner_clipped.right,
993 bottom: inner_clipped.bottom,
994 };
995 tc.thumb_height = self.framebuffer.draw_scrollbar(
996 inner_clipped,
997 track,
998 tc.scroll_offset.y,
999 tb.visual_line_count() + inner.height() - 1,
1000 );
1001 }
1002 }
1003 NodeContent::Scrollarea(sc) => {
1004 let content = node.children.first.unwrap().borrow();
1005 let track = Rect {
1006 left: inner.right,
1007 top: inner.top,
1008 right: inner.right + 1,
1009 bottom: inner.bottom,
1010 };
1011 sc.thumb_height = self.framebuffer.draw_scrollbar(
1012 outer_clipped,
1013 track,
1014 sc.scroll_offset.y,
1015 content.intrinsic_size.height,
1016 );
1017 }
1018 _ => {}
1019 }
1020
1021 for child in Tree::iterate_siblings(node.children.first) {
1022 let mut child = child.borrow_mut();
1023 self.render_node(&mut child);
1024 }
1025 }
1026
1027 fn render_styled_text(
1028 &mut self,
1029 target: Rect,
1030 actual_width: CoordType,
1031 text: &str,
1032 chunks: &[StyledTextChunk],
1033 overflow: Overflow,
1034 ) {
1035 let target_width = target.width();
1036 let mut skipped = 0..0;
1038 let mut skipped_cols = 0;
1040
1041 if overflow == Overflow::Clip || target_width >= actual_width {
1042 self.framebuffer.replace_text(target.top, target.left, target.right, text);
1043 } else {
1044 let bytes = text.as_bytes();
1045 let mut cfg = unicode::MeasurementConfig::new(&bytes);
1046
1047 match overflow {
1048 Overflow::Clip => unreachable!(),
1049 Overflow::TruncateHead => {
1050 let beg = cfg.goto_visual(Point { x: actual_width - target_width + 1, y: 0 });
1051 skipped = 0..beg.offset;
1052 skipped_cols = beg.visual_pos.x - 1;
1053 }
1054 Overflow::TruncateMiddle => {
1055 let mid_beg_x = (target_width - 1) / 2;
1056 let mid_end_x = actual_width - target_width / 2;
1057 let beg = cfg.goto_visual(Point { x: mid_beg_x, y: 0 });
1058 let end = cfg.goto_visual(Point { x: mid_end_x, y: 0 });
1059 skipped = beg.offset..end.offset;
1060 skipped_cols = end.visual_pos.x - beg.visual_pos.x - 1;
1061 }
1062 Overflow::TruncateTail => {
1063 let end = cfg.goto_visual(Point { x: target_width - 1, y: 0 });
1064 skipped_cols = actual_width - end.visual_pos.x - 1;
1065 skipped = end.offset..text.len();
1066 }
1067 }
1068
1069 let scratch = scratch_arena(None);
1070
1071 let mut modified = ArenaString::new_in(&scratch);
1072 modified.reserve(text.len() + 3);
1073 modified.push_str(&text[..skipped.start]);
1074 modified.push('…');
1075 modified.push_str(&text[skipped.end..]);
1076
1077 self.framebuffer.replace_text(target.top, target.left, target.right, &modified);
1078 }
1079
1080 if !chunks.is_empty() {
1081 let bytes = text.as_bytes();
1082 let mut cfg = unicode::MeasurementConfig::new(&bytes).with_cursor(unicode::Cursor {
1083 visual_pos: Point { x: target.left, y: 0 },
1084 ..Default::default()
1085 });
1086
1087 let mut iter = chunks.iter().peekable();
1088
1089 while let Some(chunk) = iter.next() {
1090 let beg = chunk.offset;
1091 let end = iter.peek().map_or(text.len(), |c| c.offset);
1092
1093 if beg >= skipped.start && end <= skipped.end {
1094 continue;
1097 }
1098
1099 if beg < skipped.start {
1100 let beg = cfg.goto_offset(beg).visual_pos.x;
1101 let end = cfg.goto_offset(end.min(skipped.start)).visual_pos.x;
1102 let rect =
1103 Rect { left: beg, top: target.top, right: end, bottom: target.bottom };
1104 self.framebuffer.blend_fg(rect, chunk.fg);
1105 self.framebuffer.replace_attr(rect, chunk.attr, chunk.attr);
1106 }
1107
1108 if end > skipped.end {
1109 let beg = cfg.goto_offset(beg.max(skipped.end)).visual_pos.x - skipped_cols;
1110 let end = cfg.goto_offset(end).visual_pos.x - skipped_cols;
1111 let rect =
1112 Rect { left: beg, top: target.top, right: end, bottom: target.bottom };
1113 self.framebuffer.blend_fg(rect, chunk.fg);
1114 self.framebuffer.replace_attr(rect, chunk.attr, chunk.attr);
1115 }
1116 }
1117 }
1118 }
1119
1120 pub fn debug_layout<'a>(&mut self, arena: &'a Arena) -> ArenaString<'a> {
1122 let mut result = ArenaString::new_in(arena);
1123 result.push_str("general:\r\n- focus_path:\r\n");
1124
1125 for &id in &self.focused_node_path {
1126 _ = write!(result, " - {id:016x}\r\n");
1127 }
1128
1129 result.push_str("\r\ntree:\r\n");
1130
1131 for root in self.prev_tree.iterate_roots() {
1132 Tree::visit_all(root, root, true, |node| {
1133 let node = node.borrow();
1134 let depth = node.depth;
1135 result.push_repeat(' ', depth * 2);
1136 _ = write!(result, "- id: {:016x}\r\n", node.id);
1137
1138 result.push_repeat(' ', depth * 2);
1139 _ = write!(result, " classname: {}\r\n", node.classname);
1140
1141 if depth == 0
1142 && let Some(parent) = node.parent
1143 {
1144 let parent = parent.borrow();
1145 result.push_repeat(' ', depth * 2);
1146 _ = write!(result, " parent: {:016x}\r\n", parent.id);
1147 }
1148
1149 result.push_repeat(' ', depth * 2);
1150 _ = write!(
1151 result,
1152 " intrinsic: {{{}, {}}}\r\n",
1153 node.intrinsic_size.width, node.intrinsic_size.height
1154 );
1155
1156 result.push_repeat(' ', depth * 2);
1157 _ = write!(
1158 result,
1159 " outer: {{{}, {}, {}, {}}}\r\n",
1160 node.outer.left, node.outer.top, node.outer.right, node.outer.bottom
1161 );
1162
1163 result.push_repeat(' ', depth * 2);
1164 _ = write!(
1165 result,
1166 " inner: {{{}, {}, {}, {}}}\r\n",
1167 node.inner.left, node.inner.top, node.inner.right, node.inner.bottom
1168 );
1169
1170 if node.attributes.bordered {
1171 result.push_repeat(' ', depth * 2);
1172 result.push_str(" bordered: true\r\n");
1173 }
1174
1175 if node.attributes.bg != 0 {
1176 result.push_repeat(' ', depth * 2);
1177 _ = write!(result, " bg: #{:08x}\r\n", node.attributes.bg);
1178 }
1179
1180 if node.attributes.fg != 0 {
1181 result.push_repeat(' ', depth * 2);
1182 _ = write!(result, " fg: #{:08x}\r\n", node.attributes.fg);
1183 }
1184
1185 if self.is_node_focused(node.id) {
1186 result.push_repeat(' ', depth * 2);
1187 result.push_str(" focused: true\r\n");
1188 }
1189
1190 match &node.content {
1191 NodeContent::Text(content) => {
1192 result.push_repeat(' ', depth * 2);
1193 _ = write!(result, " text: \"{}\"\r\n", &content.text);
1194 }
1195 NodeContent::Textarea(content) => {
1196 let tb = content.buffer.borrow();
1197 let tb = &*tb;
1198 result.push_repeat(' ', depth * 2);
1199 _ = write!(result, " textarea: {tb:p}\r\n");
1200 }
1201 NodeContent::Scrollarea(..) => {
1202 result.push_repeat(' ', depth * 2);
1203 result.push_str(" scrollable: true\r\n");
1204 }
1205 _ => {}
1206 }
1207
1208 VisitControl::Continue
1209 });
1210 }
1211
1212 result
1213 }
1214
1215 fn was_mouse_down_on_node(&self, id: u64) -> bool {
1216 self.mouse_down_node_path.last() == Some(&id)
1217 }
1218
1219 fn was_mouse_down_on_subtree(&self, node: &Node) -> bool {
1220 self.mouse_down_node_path.get(node.depth) == Some(&node.id)
1221 }
1222
1223 fn is_node_focused(&self, id: u64) -> bool {
1224 unsafe { *self.focused_node_path.last().unwrap_unchecked() == id }
1226 }
1227
1228 fn is_subtree_focused(&self, node: &Node) -> bool {
1229 self.focused_node_path.get(node.depth) == Some(&node.id)
1230 }
1231
1232 fn is_subtree_focused_alt(&self, id: u64, depth: usize) -> bool {
1233 self.focused_node_path.get(depth) == Some(&id)
1234 }
1235
1236 fn pop_focusable_node(&mut self, pop_minimum: usize) -> bool {
1237 let last_before = self.focused_node_path.last().cloned().unwrap_or(0);
1238
1239 let path = &self.focused_node_path[..];
1241 let path = &path[..path.len().saturating_sub(pop_minimum)];
1242 let mut len = 0;
1243
1244 for (i, &id) in path.iter().enumerate() {
1245 let Some(node) = self.prev_node_map.get(id) else {
1247 break;
1248 };
1249
1250 let n = node.borrow();
1251 if pop_minimum != 0 && n.attributes.focus_void {
1254 break;
1255 }
1256
1257 if n.attributes.focusable {
1259 len = i + 1;
1262 }
1263 }
1264
1265 self.focused_node_path.truncate(len);
1266
1267 if self.focused_node_path.is_empty() {
1269 self.focused_node_path.push(ROOT_ID);
1270 }
1271
1272 let last_after = self.focused_node_path.last().cloned().unwrap_or(0);
1274 last_before != last_after
1275 }
1276
1277 fn scroll_to_focused(&mut self) -> bool {
1279 let focused_id = self.focused_node_path.last().cloned().unwrap_or(0);
1280 if self.focused_node_for_scrolling == focused_id {
1281 return false;
1282 }
1283
1284 let Some(node) = self.prev_node_map.get(focused_id) else {
1285 return true;
1288 };
1289
1290 let mut node = node.borrow_mut();
1291 let mut scroll_to = node.outer;
1292
1293 while node.parent.is_some() && node.attributes.float.is_none() {
1294 let n = &mut *node;
1295 if let NodeContent::Scrollarea(sc) = &mut n.content {
1296 let off_y = sc.scroll_offset.y.max(0);
1297 let mut y = off_y;
1298 y = y.min(scroll_to.top - n.inner.top + off_y);
1299 y = y.max(scroll_to.bottom - n.inner.bottom + off_y);
1300 sc.scroll_offset.y = y;
1301 scroll_to = n.outer;
1302 }
1303 node = node.parent.unwrap().borrow_mut();
1304 }
1305
1306 self.focused_node_for_scrolling = focused_id;
1307 true
1308 }
1309}
1310
1311pub struct Context<'a, 'input> {
1314 tui: &'a mut Tui,
1315
1316 input_text: Option<&'input str>,
1318 input_keyboard: Option<InputKey>,
1320 input_mouse_modifiers: InputKeyMod,
1321 input_mouse_click: CoordType,
1322 input_scroll_delta: Point,
1324 input_consumed: bool,
1325
1326 tree: Tree<'a>,
1327 last_modal: Option<&'a NodeCell<'a>>,
1328 focused_node: Option<&'a NodeCell<'a>>,
1329 next_block_id_mixin: u64,
1330 needs_settling: bool,
1331
1332 #[cfg(debug_assertions)]
1333 seen_ids: HashSet<u64>,
1334}
1335
1336impl<'a> Drop for Context<'a, '_> {
1337 fn drop(&mut self) {
1338 let tui: &'a mut Tui = unsafe { mem::transmute(&mut *self.tui) };
1339 tui.report_context_completion(self);
1340 }
1341}
1342
1343impl<'a> Context<'a, '_> {
1344 pub fn arena(&self) -> &'a Arena {
1346 unsafe { mem::transmute::<&'_ Arena, &'a Arena>(&self.tui.arena_next) }
1353 }
1354
1355 pub fn size(&self) -> Size {
1357 self.tui.size()
1358 }
1359
1360 #[inline]
1362 pub fn indexed(&self, index: IndexedColor) -> u32 {
1363 self.tui.framebuffer.indexed(index)
1364 }
1365
1366 #[inline]
1369 pub fn indexed_alpha(&self, index: IndexedColor, numerator: u32, denominator: u32) -> u32 {
1370 self.tui.framebuffer.indexed_alpha(index, numerator, denominator)
1371 }
1372
1373 pub fn contrasted(&self, color: u32) -> u32 {
1376 self.tui.framebuffer.contrasted(color)
1377 }
1378
1379 pub fn clipboard_ref(&self) -> &Clipboard {
1381 &self.tui.clipboard
1382 }
1383
1384 pub fn clipboard_mut(&mut self) -> &mut Clipboard {
1386 &mut self.tui.clipboard
1387 }
1388
1389 pub fn needs_rerender(&mut self) {
1391 debug_assert!(self.tui.settling_have < 15);
1393 self.needs_settling = true;
1394 }
1395
1396 pub fn block_begin(&mut self, classname: &'static str) {
1398 let parent = self.tree.current_node;
1399
1400 let mut id = hash_str(parent.borrow().id, classname);
1401 if self.next_block_id_mixin != 0 {
1402 id = hash(id, &self.next_block_id_mixin.to_ne_bytes());
1403 self.next_block_id_mixin = 0;
1404 }
1405
1406 #[cfg(debug_assertions)]
1410 if !self.seen_ids.insert(id) {
1411 panic!("Duplicate node ID: {id:x}");
1412 }
1413
1414 let node = Tree::alloc_node(self.arena());
1415 {
1416 let mut n = node.borrow_mut();
1417 n.id = id;
1418 n.classname = classname;
1419 }
1420
1421 self.tree.push_child(node);
1422 }
1423
1424 pub fn block_end(&mut self) {
1426 self.tree.pop_stack();
1427 self.block_end_move_focus();
1428 }
1429
1430 fn block_end_move_focus(&mut self) {
1431 let focus_well = self.tree.last_node;
1433
1434 if self.is_focused() {
1437 self.focused_node = Some(focus_well);
1438 }
1439
1440 let Some(focused) = self.focused_node else {
1443 return;
1444 };
1445
1446 {
1450 let n = focus_well.borrow();
1451 if !n.attributes.focus_well || n.depth > focused.borrow().depth {
1452 return;
1453 }
1454 }
1455
1456 if self.input_consumed {
1458 return;
1459 }
1460 let Some(input) = self.input_keyboard else {
1461 return;
1462 };
1463 if !matches!(input, SHIFT_TAB | vk::TAB) {
1464 return;
1465 }
1466
1467 let forward = input == vk::TAB;
1468 let mut focused_start = focused;
1469 let mut focused_next = focused;
1470
1471 loop {
1474 if ptr::eq(focused_start, focus_well) {
1475 focused_start = focused;
1478 break;
1479 }
1480
1481 focused_start = focused_start.borrow().parent.unwrap();
1482 if focused_start.borrow().attributes.focus_void {
1483 break;
1484 }
1485 }
1486
1487 Tree::visit_all(focus_well, focused_start, forward, |node| {
1488 let n = node.borrow();
1489 if n.attributes.focusable && !ptr::eq(node, focused_start) {
1490 focused_next = node;
1491 VisitControl::Stop
1492 } else if n.attributes.focus_void {
1493 VisitControl::SkipChildren
1494 } else {
1495 VisitControl::Continue
1496 }
1497 });
1498
1499 if ptr::eq(focused_next, focused_start) {
1500 return;
1501 }
1502
1503 Tui::build_node_path(Some(focused_next), &mut self.tui.focused_node_path);
1504 self.set_input_consumed();
1505 self.needs_rerender();
1506 }
1507
1508 pub fn next_block_id_mixin(&mut self, id: u64) {
1511 self.next_block_id_mixin = id;
1512 }
1513
1514 fn attr_focusable(&mut self) {
1515 let mut last_node = self.tree.last_node.borrow_mut();
1516 last_node.attributes.focusable = true;
1517 }
1518
1519 pub fn focus_on_first_present(&mut self) {
1522 let steal = {
1523 let mut last_node = self.tree.last_node.borrow_mut();
1524 last_node.attributes.focusable = true;
1525 self.tui.prev_node_map.get(last_node.id).is_none()
1526 };
1527 if steal {
1528 self.steal_focus();
1529 }
1530 }
1531
1532 pub fn steal_focus(&mut self) {
1534 self.steal_focus_for(self.tree.last_node);
1535 }
1536
1537 fn steal_focus_for(&mut self, node: &NodeCell<'a>) {
1538 if !self.tui.is_node_focused(node.borrow().id) {
1539 Tui::build_node_path(Some(node), &mut self.tui.focused_node_path);
1540 self.needs_rerender();
1541 }
1542 }
1543
1544 pub fn toss_focus_up(&mut self) {
1546 if self.tui.pop_focusable_node(1) {
1547 self.needs_rerender();
1548 }
1549 }
1550
1551 pub fn inherit_focus(&mut self) {
1553 let mut last_node = self.tree.last_node.borrow_mut();
1554 let Some(parent) = last_node.parent else {
1555 return;
1556 };
1557
1558 last_node.attributes.focusable = true;
1559
1560 let mut parent = parent.borrow_mut();
1564 parent.attributes.focusable = true;
1565
1566 if self.tui.is_node_focused(parent.id) {
1567 self.needs_rerender();
1568 self.tui.focused_node_path.push(last_node.id);
1569 }
1570 }
1571
1572 pub fn attr_focus_well(&mut self) {
1575 let mut last_node = self.tree.last_node.borrow_mut();
1576 last_node.attributes.focus_well = true;
1577 }
1578
1579 pub fn attr_intrinsic_size(&mut self, size: Size) {
1582 let mut last_node = self.tree.last_node.borrow_mut();
1583 last_node.intrinsic_size = size;
1584 last_node.intrinsic_size_set = true;
1585 }
1586
1587 pub fn attr_float(&mut self, spec: FloatSpec) {
1590 let last_node = self.tree.last_node;
1591 let anchor = {
1592 let ln = last_node.borrow();
1593 match spec.anchor {
1594 Anchor::Last if ln.siblings.prev.is_some() => ln.siblings.prev,
1595 Anchor::Last | Anchor::Parent => ln.parent,
1596 Anchor::Root => None,
1600 }
1601 };
1602
1603 self.tree.move_node_to_root(last_node, anchor);
1604
1605 let mut ln = last_node.borrow_mut();
1606 ln.attributes.focus_well = true;
1607 ln.attributes.float = Some(FloatAttributes {
1608 gravity_x: spec.gravity_x.clamp(0.0, 1.0),
1609 gravity_y: spec.gravity_y.clamp(0.0, 1.0),
1610 offset_x: spec.offset_x,
1611 offset_y: spec.offset_y,
1612 });
1613 ln.attributes.bg = self.tui.floater_default_bg;
1614 ln.attributes.fg = self.tui.floater_default_fg;
1615 }
1616
1617 pub fn attr_border(&mut self) {
1619 let mut last_node = self.tree.last_node.borrow_mut();
1620 last_node.attributes.bordered = true;
1621 }
1622
1623 pub fn attr_position(&mut self, align: Position) {
1625 let mut last_node = self.tree.last_node.borrow_mut();
1626 last_node.attributes.position = align;
1627 }
1628
1629 pub fn attr_padding(&mut self, padding: Rect) {
1631 let mut last_node = self.tree.last_node.borrow_mut();
1632 last_node.attributes.padding = Self::normalize_rect(padding);
1633 }
1634
1635 fn normalize_rect(rect: Rect) -> Rect {
1636 Rect {
1637 left: rect.left.max(0),
1638 top: rect.top.max(0),
1639 right: rect.right.max(0),
1640 bottom: rect.bottom.max(0),
1641 }
1642 }
1643
1644 pub fn attr_background_rgba(&mut self, bg: u32) {
1646 let mut last_node = self.tree.last_node.borrow_mut();
1647 last_node.attributes.bg = bg;
1648 }
1649
1650 pub fn attr_foreground_rgba(&mut self, fg: u32) {
1652 let mut last_node = self.tree.last_node.borrow_mut();
1653 last_node.attributes.fg = fg;
1654 }
1655
1656 pub fn attr_reverse(&mut self) {
1659 let mut last_node = self.tree.last_node.borrow_mut();
1660 last_node.attributes.reverse = true;
1661 }
1662
1663 pub fn consume_shortcut(&mut self, shortcut: InputKey) -> bool {
1666 if !self.input_consumed && self.input_keyboard == Some(shortcut) {
1667 self.set_input_consumed();
1668 true
1669 } else {
1670 false
1671 }
1672 }
1673
1674 pub fn keyboard_input(&self) -> Option<InputKey> {
1677 if self.input_consumed { None } else { self.input_keyboard }
1678 }
1679
1680 #[inline]
1681 pub fn set_input_consumed(&mut self) {
1682 debug_assert!(!self.input_consumed);
1683 self.set_input_consumed_unchecked();
1684 }
1685
1686 #[inline]
1687 fn set_input_consumed_unchecked(&mut self) {
1688 self.input_consumed = true;
1689 }
1690
1691 pub fn was_mouse_down(&mut self) -> bool {
1693 let last_node = self.tree.last_node.borrow();
1694 self.tui.was_mouse_down_on_node(last_node.id)
1695 }
1696
1697 pub fn contains_mouse_down(&mut self) -> bool {
1699 let last_node = self.tree.last_node.borrow();
1700 self.tui.was_mouse_down_on_subtree(&last_node)
1701 }
1702
1703 pub fn is_focused(&mut self) -> bool {
1705 let last_node = self.tree.last_node.borrow();
1706 self.tui.is_node_focused(last_node.id)
1707 }
1708
1709 pub fn contains_focus(&mut self) -> bool {
1711 let last_node = self.tree.last_node.borrow();
1712 self.tui.is_subtree_focused(&last_node)
1713 }
1714
1715 pub fn modal_begin(&mut self, classname: &'static str, title: &str) {
1717 self.block_begin(classname);
1718 self.attr_float(FloatSpec {
1719 anchor: Anchor::Root,
1720 gravity_x: 0.5,
1721 gravity_y: 0.5,
1722 offset_x: self.tui.size.width as f32 * 0.5,
1723 offset_y: self.tui.size.height as f32 * 0.5,
1724 });
1725 self.attr_border();
1726 self.attr_background_rgba(self.tui.modal_default_bg);
1727 self.attr_foreground_rgba(self.tui.modal_default_fg);
1728 self.attr_focus_well();
1729 self.focus_on_first_present();
1730
1731 let mut last_node = self.tree.last_node.borrow_mut();
1732 let title = if title.is_empty() {
1733 ArenaString::new_in(self.arena())
1734 } else {
1735 arena_format!(self.arena(), " {} ", title)
1736 };
1737 last_node.content = NodeContent::Modal(title);
1738 self.last_modal = Some(self.tree.last_node);
1739 }
1740
1741 pub fn modal_end(&mut self) -> bool {
1744 self.block_end();
1745
1746 if self.contains_focus() {
1749 let exit = !self.input_consumed && self.input_keyboard == Some(vk::ESCAPE);
1750 self.set_input_consumed_unchecked();
1751 exit
1752 } else {
1753 false
1754 }
1755 }
1756
1757 pub fn table_begin(&mut self, classname: &'static str) {
1761 self.block_begin(classname);
1762
1763 let mut last_node = self.tree.last_node.borrow_mut();
1764 last_node.content = NodeContent::Table(TableContent {
1765 columns: Vec::new_in(self.arena()),
1766 cell_gap: Default::default(),
1767 });
1768 }
1769
1770 pub fn table_set_columns(&mut self, columns: &[CoordType]) {
1773 let mut last_node = self.tree.last_node.borrow_mut();
1774 if let NodeContent::Table(spec) = &mut last_node.content {
1775 spec.columns.clear();
1776 spec.columns.extend_from_slice(columns);
1777 } else {
1778 debug_assert!(false);
1779 }
1780 }
1781
1782 pub fn table_set_cell_gap(&mut self, cell_gap: Size) {
1784 let mut last_node = self.tree.last_node.borrow_mut();
1785 if let NodeContent::Table(spec) = &mut last_node.content {
1786 spec.cell_gap = cell_gap;
1787 } else {
1788 debug_assert!(false);
1789 }
1790 }
1791
1792 pub fn table_next_row(&mut self) {
1794 {
1795 let current_node = self.tree.current_node.borrow();
1796
1797 if !matches!(current_node.content, NodeContent::Table(_)) {
1800 let Some(parent) = current_node.parent else {
1801 return;
1802 };
1803
1804 let parent = parent.borrow();
1805 debug_assert!(matches!(parent.content, NodeContent::Table(_)));
1808
1809 self.block_end();
1810 self.table_end_row();
1811
1812 self.next_block_id_mixin(parent.child_count as u64);
1813 }
1814 }
1815
1816 self.block_begin("row");
1817 }
1818
1819 fn table_end_row(&mut self) {
1820 self.table_move_focus(vk::LEFT, vk::RIGHT);
1821 }
1822
1823 pub fn table_end(&mut self) {
1825 let current_node = self.tree.current_node.borrow();
1826
1827 if !matches!(current_node.content, NodeContent::Table(_)) {
1830 self.block_end();
1831 self.table_end_row();
1832 }
1833
1834 self.block_end(); self.table_move_focus(vk::UP, vk::DOWN);
1836 }
1837
1838 fn table_move_focus(&mut self, prev_key: InputKey, next_key: InputKey) {
1839 if !self.contains_focus() {
1841 return;
1842 }
1843
1844 if self.input_consumed {
1846 return;
1847 }
1848 let Some(input) = self.input_keyboard else {
1849 return;
1850 };
1851 if input != prev_key && input != next_key {
1852 return;
1853 }
1854
1855 let container = self.tree.last_node;
1856 let Some(&focused_id) = self.tui.focused_node_path.get(container.borrow().depth + 1) else {
1857 return;
1858 };
1859
1860 let mut prev_next = NodeSiblings { prev: None, next: None };
1861 let mut focused = None;
1862
1863 for cell in Tree::iterate_siblings(container.borrow().children.first) {
1866 let n = cell.borrow();
1867 if n.id == focused_id {
1868 focused = Some(cell);
1869 } else if n.attributes.focusable {
1870 if focused.is_none() {
1871 prev_next.prev = Some(cell);
1872 } else {
1873 prev_next.next = Some(cell);
1874 break;
1875 }
1876 }
1877 }
1878
1879 if focused.is_none() {
1880 return;
1881 }
1882
1883 let forward = input == next_key;
1884 let children_idx = if forward { NodeChildren::FIRST } else { NodeChildren::LAST };
1885 let siblings_idx = if forward { NodeSiblings::NEXT } else { NodeSiblings::PREV };
1886 let Some(focused_next) =
1887 prev_next.get(siblings_idx).or_else(|| container.borrow().children.get(children_idx))
1888 else {
1889 return;
1890 };
1891
1892 Tui::build_node_path(Some(focused_next), &mut self.tui.focused_node_path);
1893 self.set_input_consumed();
1894 self.needs_rerender();
1895 }
1896
1897 pub fn label(&mut self, classname: &'static str, text: &str) {
1899 self.styled_label_begin(classname);
1900 self.styled_label_add_text(text);
1901 self.styled_label_end();
1902 }
1903
1904 pub fn styled_label_begin(&mut self, classname: &'static str) {
1921 self.block_begin(classname);
1922 self.tree.last_node.borrow_mut().content = NodeContent::Text(TextContent {
1923 text: ArenaString::new_in(self.arena()),
1924 chunks: Vec::with_capacity_in(4, self.arena()),
1925 overflow: Overflow::Clip,
1926 });
1927 }
1928
1929 pub fn styled_label_set_foreground(&mut self, fg: u32) {
1931 let mut node = self.tree.last_node.borrow_mut();
1932 let NodeContent::Text(content) = &mut node.content else {
1933 unreachable!();
1934 };
1935
1936 let last = content.chunks.last().unwrap_or(&INVALID_STYLED_TEXT_CHUNK);
1937 if last.offset != content.text.len() && last.fg != fg {
1938 content.chunks.push(StyledTextChunk {
1939 offset: content.text.len(),
1940 fg,
1941 attr: last.attr,
1942 });
1943 }
1944 }
1945
1946 pub fn styled_label_set_attributes(&mut self, attr: Attributes) {
1948 let mut node = self.tree.last_node.borrow_mut();
1949 let NodeContent::Text(content) = &mut node.content else {
1950 unreachable!();
1951 };
1952
1953 let last = content.chunks.last().unwrap_or(&INVALID_STYLED_TEXT_CHUNK);
1954 if last.offset != content.text.len() && last.attr != attr {
1955 content.chunks.push(StyledTextChunk { offset: content.text.len(), fg: last.fg, attr });
1956 }
1957 }
1958
1959 pub fn styled_label_add_text(&mut self, text: &str) {
1961 let mut node = self.tree.last_node.borrow_mut();
1962 let NodeContent::Text(content) = &mut node.content else {
1963 unreachable!();
1964 };
1965
1966 content.text.push_str(text);
1967 }
1968
1969 pub fn styled_label_end(&mut self) {
1971 {
1972 let mut last_node = self.tree.last_node.borrow_mut();
1973 let NodeContent::Text(content) = &last_node.content else {
1974 return;
1975 };
1976
1977 let cursor = unicode::MeasurementConfig::new(&content.text.as_bytes())
1978 .goto_visual(Point { x: CoordType::MAX, y: 0 });
1979 last_node.intrinsic_size.width = cursor.visual_pos.x;
1980 last_node.intrinsic_size.height = 1;
1981 last_node.intrinsic_size_set = true;
1982 }
1983
1984 self.block_end();
1985 }
1986
1987 pub fn attr_overflow(&mut self, overflow: Overflow) {
1989 let mut last_node = self.tree.last_node.borrow_mut();
1990 let NodeContent::Text(content) = &mut last_node.content else {
1991 return;
1992 };
1993
1994 content.overflow = overflow;
1995 }
1996
1997 pub fn button(&mut self, classname: &'static str, text: &str, style: ButtonStyle) -> bool {
2000 self.button_label(classname, text, style);
2001 self.attr_focusable();
2002 if self.is_focused() {
2003 self.attr_reverse();
2004 }
2005 self.button_activated()
2006 }
2007
2008 pub fn checkbox(&mut self, classname: &'static str, text: &str, checked: &mut bool) -> bool {
2011 self.styled_label_begin(classname);
2012 self.attr_focusable();
2013 if self.is_focused() {
2014 self.attr_reverse();
2015 }
2016 self.styled_label_add_text(if *checked { "[🗹 " } else { "[☐ " });
2017 self.styled_label_add_text(text);
2018 self.styled_label_add_text("]");
2019 self.styled_label_end();
2020
2021 let activated = self.button_activated();
2022 if activated {
2023 *checked = !*checked;
2024 }
2025 activated
2026 }
2027
2028 fn button_activated(&mut self) -> bool {
2029 if !self.input_consumed
2030 && ((self.input_mouse_click != 0 && self.contains_mouse_down())
2031 || self.input_keyboard == Some(vk::RETURN)
2032 || self.input_keyboard == Some(vk::SPACE))
2033 && self.is_focused()
2034 {
2035 self.set_input_consumed();
2036 true
2037 } else {
2038 false
2039 }
2040 }
2041
2042 pub fn editline(&mut self, classname: &'static str, text: &mut dyn WriteableDocument) -> bool {
2045 self.textarea_internal(classname, TextBufferPayload::Editline(text))
2046 }
2047
2048 pub fn textarea(&mut self, classname: &'static str, tb: RcTextBuffer) {
2050 self.textarea_internal(classname, TextBufferPayload::Textarea(tb));
2051 }
2052
2053 fn textarea_internal(&mut self, classname: &'static str, payload: TextBufferPayload) -> bool {
2054 self.block_begin(classname);
2055 self.block_end();
2056
2057 let mut node = self.tree.last_node.borrow_mut();
2058 let node = &mut *node;
2059 let single_line = match &payload {
2060 TextBufferPayload::Editline(_) => true,
2061 TextBufferPayload::Textarea(_) => false,
2062 };
2063
2064 let buffer = {
2065 let buffers = &mut self.tui.cached_text_buffers;
2066
2067 let cached = match buffers.iter_mut().find(|t| t.node_id == node.id) {
2068 Some(cached) => {
2069 if let TextBufferPayload::Textarea(tb) = &payload {
2070 cached.editor = tb.clone();
2071 };
2072 cached.seen = true;
2073 cached
2074 }
2075 None => {
2076 buffers.push(CachedTextBuffer {
2078 node_id: node.id,
2079 editor: match &payload {
2080 TextBufferPayload::Editline(_) => TextBuffer::new_rc(true).unwrap(),
2081 TextBufferPayload::Textarea(tb) => tb.clone(),
2082 },
2083 seen: true,
2084 });
2085 buffers.last_mut().unwrap()
2086 }
2087 };
2088
2089 unsafe { mem::transmute(&*cached.editor) }
2093 };
2094
2095 node.content = NodeContent::Textarea(TextareaContent {
2096 buffer,
2097 scroll_offset: Default::default(),
2098 scroll_offset_y_drag_start: CoordType::MIN,
2099 scroll_offset_x_max: 0,
2100 thumb_height: 0,
2101 preferred_column: 0,
2102 single_line,
2103 has_focus: self.tui.is_node_focused(node.id),
2104 });
2105
2106 let content = match node.content {
2107 NodeContent::Textarea(ref mut content) => content,
2108 _ => unreachable!(),
2109 };
2110
2111 if let TextBufferPayload::Editline(text) = &payload {
2112 content.buffer.borrow_mut().copy_from_str(*text);
2113 }
2114
2115 if let Some(node_prev) = self.tui.prev_node_map.get(node.id) {
2116 let node_prev = node_prev.borrow();
2117 if let NodeContent::Textarea(content_prev) = &node_prev.content {
2118 content.scroll_offset = content_prev.scroll_offset;
2119 content.scroll_offset_y_drag_start = content_prev.scroll_offset_y_drag_start;
2120 content.scroll_offset_x_max = content_prev.scroll_offset_x_max;
2121 content.thumb_height = content_prev.thumb_height;
2122 content.preferred_column = content_prev.preferred_column;
2123
2124 let mut text_width = node_prev.inner.width();
2125 if !single_line {
2126 text_width -= 1;
2128 }
2129
2130 let mut make_cursor_visible;
2131 {
2132 let mut tb = content.buffer.borrow_mut();
2133 make_cursor_visible = tb.take_cursor_visibility_request();
2134 make_cursor_visible |= tb.set_width(text_width);
2135 }
2136
2137 make_cursor_visible |= self.textarea_handle_input(content, &node_prev, single_line);
2138
2139 if make_cursor_visible {
2140 self.textarea_make_cursor_visible(content, &node_prev);
2141 }
2142 } else {
2143 debug_assert!(false);
2144 }
2145 }
2146
2147 let dirty;
2148 {
2149 let mut tb = content.buffer.borrow_mut();
2150 dirty = tb.is_dirty();
2151 if dirty && let TextBufferPayload::Editline(text) = payload {
2152 tb.save_as_string(text);
2153 }
2154 }
2155
2156 self.textarea_adjust_scroll_offset(content);
2157
2158 if single_line {
2159 node.attributes.fg = self.indexed(IndexedColor::Foreground);
2160 node.attributes.bg = self.indexed(IndexedColor::Background);
2161 if !content.has_focus {
2162 node.attributes.fg = self.contrasted(node.attributes.bg);
2163 node.attributes.bg = self.indexed_alpha(IndexedColor::Background, 1, 2);
2164 }
2165 }
2166
2167 node.attributes.focusable = true;
2168 node.intrinsic_size.height = content.buffer.borrow().visual_line_count();
2169 node.intrinsic_size_set = true;
2170
2171 dirty
2172 }
2173
2174 fn textarea_handle_input(
2175 &mut self,
2176 tc: &mut TextareaContent,
2177 node_prev: &Node,
2178 single_line: bool,
2179 ) -> bool {
2180 if self.input_consumed {
2181 return false;
2182 }
2183
2184 let mut tb = tc.buffer.borrow_mut();
2185 let tb = &mut *tb;
2186 let mut make_cursor_visible = false;
2187 let mut change_preferred_column = false;
2188
2189 if self.tui.mouse_state != InputMouseState::None
2190 && self.tui.was_mouse_down_on_node(node_prev.id)
2191 {
2192 if self.tui.mouse_state == InputMouseState::Scroll {
2194 tc.scroll_offset.x += self.input_scroll_delta.x;
2195 tc.scroll_offset.y += self.input_scroll_delta.y;
2196 self.set_input_consumed();
2197 } else if self.tui.is_node_focused(node_prev.id) {
2198 let mouse = self.tui.mouse_position;
2199 let inner = node_prev.inner;
2200 let text_rect = Rect {
2201 left: inner.left + tb.margin_width(),
2202 top: inner.top,
2203 right: inner.right - !single_line as CoordType,
2204 bottom: inner.bottom,
2205 };
2206 let track_rect = Rect {
2207 left: text_rect.right,
2208 top: inner.top,
2209 right: inner.right,
2210 bottom: inner.bottom,
2211 };
2212 let pos = Point {
2213 x: mouse.x - inner.left - tb.margin_width() + tc.scroll_offset.x,
2214 y: mouse.y - inner.top + tc.scroll_offset.y,
2215 };
2216
2217 if text_rect.contains(self.tui.mouse_down_position) {
2218 if self.tui.mouse_is_drag {
2219 tb.selection_update_visual(pos);
2220 tc.preferred_column = tb.cursor_visual_pos().x;
2221
2222 let height = inner.height();
2223
2224 if height >= 2 {
2226 fn calc(min: CoordType, max: CoordType, mouse: CoordType) -> CoordType {
2227 let zone_height = ((max - min) / 2).min(3);
2229
2230 let scroll_min = min + zone_height;
2233 let scroll_max = max - zone_height - 1;
2234
2235 let delta_min = (mouse - scroll_min).clamp(-zone_height, 0);
2237 let delta_max = (mouse - scroll_max).clamp(0, zone_height);
2238
2239 let idx = 3 + delta_min + delta_max;
2241
2242 const SPEEDS: [CoordType; 7] = [-9, -3, -1, 0, 1, 3, 9];
2243 let idx = idx.clamp(0, SPEEDS.len() as CoordType) as usize;
2244 SPEEDS[idx]
2245 }
2246
2247 let delta_x = calc(text_rect.left, text_rect.right, mouse.x);
2248 let delta_y = calc(text_rect.top, text_rect.bottom, mouse.y);
2249
2250 tc.scroll_offset.x += delta_x;
2251 tc.scroll_offset.y += delta_y;
2252
2253 if delta_x != 0 || delta_y != 0 {
2254 self.tui.read_timeout = time::Duration::from_millis(25);
2255 }
2256 }
2257 } else {
2258 match self.input_mouse_click {
2259 5.. => {}
2260 4 => tb.select_all(),
2261 3 => tb.select_line(),
2262 2 => tb.select_word(),
2263 _ => match self.tui.mouse_state {
2264 InputMouseState::Left => {
2265 if self.input_mouse_modifiers.contains(kbmod::SHIFT) {
2266 tb.selection_update_visual(pos);
2268 } else {
2269 tb.cursor_move_to_visual(pos);
2270 }
2271 tc.preferred_column = tb.cursor_visual_pos().x;
2272 make_cursor_visible = true;
2273 }
2274 _ => return false,
2275 },
2276 }
2277 }
2278 } else if track_rect.contains(self.tui.mouse_down_position) {
2279 if self.tui.mouse_state == InputMouseState::Release {
2280 tc.scroll_offset_y_drag_start = CoordType::MIN;
2281 } else if self.tui.mouse_is_drag {
2282 if tc.scroll_offset_y_drag_start == CoordType::MIN {
2283 tc.scroll_offset_y_drag_start = tc.scroll_offset.y;
2284 }
2285
2286 let scrollable_height = tb.visual_line_count() - 1;
2289
2290 if scrollable_height > 0 {
2291 let trackable = track_rect.height() - tc.thumb_height;
2292 let delta_y = mouse.y - self.tui.mouse_down_position.y;
2293 tc.scroll_offset.y = tc.scroll_offset_y_drag_start
2294 + (delta_y as i64 * scrollable_height as i64 / trackable as i64)
2295 as CoordType;
2296 }
2297 }
2298 }
2299
2300 self.set_input_consumed();
2301 }
2302
2303 return make_cursor_visible;
2304 }
2305
2306 if !tc.has_focus {
2307 return false;
2308 }
2309
2310 let mut write: &[u8] = &[];
2311
2312 if let Some(input) = &self.input_text {
2313 write = input.as_bytes();
2314 } else if let Some(input) = &self.input_keyboard {
2315 let key = input.key();
2316 let modifiers = input.modifiers();
2317
2318 make_cursor_visible = true;
2319
2320 match key {
2321 vk::BACK => {
2322 let granularity = if modifiers == kbmod::CTRL {
2323 CursorMovement::Word
2324 } else {
2325 CursorMovement::Grapheme
2326 };
2327 tb.delete(granularity, -1);
2328 }
2329 vk::TAB => {
2330 if single_line {
2331 return false;
2333 }
2334 if modifiers == kbmod::SHIFT {
2335 tb.unindent();
2336 } else {
2337 write = b"\t";
2338 }
2339 }
2340 vk::RETURN => {
2341 if single_line {
2342 return false;
2344 }
2345 write = b"\n";
2346 }
2347 vk::ESCAPE => {
2348 if !tb.clear_selection() {
2350 if single_line {
2351 return false;
2354 }
2355
2356 make_cursor_visible = false;
2359 }
2360 }
2361 vk::PRIOR => {
2362 let height = node_prev.inner.height() - 1;
2363
2364 if tb.cursor_visual_pos().y == 0 {
2367 tc.preferred_column = 0;
2368 }
2369
2370 if modifiers == kbmod::SHIFT {
2371 tb.selection_update_visual(Point {
2372 x: tc.preferred_column,
2373 y: tb.cursor_visual_pos().y - height,
2374 });
2375 } else {
2376 tb.cursor_move_to_visual(Point {
2377 x: tc.preferred_column,
2378 y: tb.cursor_visual_pos().y - height,
2379 });
2380 }
2381 }
2382 vk::NEXT => {
2383 let height = node_prev.inner.height() - 1;
2384
2385 if tb.cursor_visual_pos().y >= tb.visual_line_count() - 1 {
2388 tc.preferred_column = CoordType::MAX;
2389 }
2390
2391 if modifiers == kbmod::SHIFT {
2392 tb.selection_update_visual(Point {
2393 x: tc.preferred_column,
2394 y: tb.cursor_visual_pos().y + height,
2395 });
2396 } else {
2397 tb.cursor_move_to_visual(Point {
2398 x: tc.preferred_column,
2399 y: tb.cursor_visual_pos().y + height,
2400 });
2401 }
2402
2403 if tc.preferred_column == CoordType::MAX {
2404 tc.preferred_column = tb.cursor_visual_pos().x;
2405 }
2406 }
2407 vk::END => {
2408 let logical_before = tb.cursor_logical_pos();
2409 let destination = if modifiers.contains(kbmod::CTRL) {
2410 Point::MAX
2411 } else {
2412 Point { x: CoordType::MAX, y: tb.cursor_visual_pos().y }
2413 };
2414
2415 if modifiers.contains(kbmod::SHIFT) {
2416 tb.selection_update_visual(destination);
2417 } else {
2418 tb.cursor_move_to_visual(destination);
2419 }
2420
2421 if !modifiers.contains(kbmod::CTRL) {
2422 let logical_after = tb.cursor_logical_pos();
2423
2424 if tb.is_word_wrap_enabled() && logical_after == logical_before {
2428 if modifiers == kbmod::SHIFT {
2429 tb.selection_update_logical(Point {
2430 x: CoordType::MAX,
2431 y: tb.cursor_logical_pos().y,
2432 });
2433 } else {
2434 tb.cursor_move_to_logical(Point {
2435 x: CoordType::MAX,
2436 y: tb.cursor_logical_pos().y,
2437 });
2438 }
2439 }
2440 }
2441 }
2442 vk::HOME => {
2443 let logical_before = tb.cursor_logical_pos();
2444 let destination = if modifiers.contains(kbmod::CTRL) {
2445 Default::default()
2446 } else {
2447 Point { x: 0, y: tb.cursor_visual_pos().y }
2448 };
2449
2450 if modifiers.contains(kbmod::SHIFT) {
2451 tb.selection_update_visual(destination);
2452 } else {
2453 tb.cursor_move_to_visual(destination);
2454 }
2455
2456 if !modifiers.contains(kbmod::CTRL) {
2457 let mut logical_after = tb.cursor_logical_pos();
2458
2459 if tb.is_word_wrap_enabled() && logical_after == logical_before {
2463 if modifiers == kbmod::SHIFT {
2464 tb.selection_update_logical(Point {
2465 x: 0,
2466 y: tb.cursor_logical_pos().y,
2467 });
2468 } else {
2469 tb.cursor_move_to_logical(Point {
2470 x: 0,
2471 y: tb.cursor_logical_pos().y,
2472 });
2473 }
2474 logical_after = tb.cursor_logical_pos();
2475 }
2476
2477 if logical_after.x == 0
2484 && let indent_end = tb.indent_end_logical_pos()
2485 && (logical_before > indent_end || logical_before.x == 0)
2486 {
2487 if modifiers == kbmod::SHIFT {
2488 tb.selection_update_logical(indent_end);
2489 } else {
2490 tb.cursor_move_to_logical(indent_end);
2491 }
2492 }
2493 }
2494 }
2495 vk::LEFT => {
2496 let granularity = if modifiers.contains(KBMOD_FOR_WORD_NAV) {
2497 CursorMovement::Word
2498 } else {
2499 CursorMovement::Grapheme
2500 };
2501 if modifiers.contains(kbmod::SHIFT) {
2502 tb.selection_update_delta(granularity, -1);
2503 } else if let Some((beg, _)) = tb.selection_range() {
2504 unsafe { tb.set_cursor(beg) };
2505 } else {
2506 tb.cursor_move_delta(granularity, -1);
2507 }
2508 }
2509 vk::UP => {
2510 if single_line {
2511 return false;
2512 }
2513 match modifiers {
2514 kbmod::NONE => {
2515 let mut x = tc.preferred_column;
2516 let mut y = tb.cursor_visual_pos().y - 1;
2517
2518 if let Some((beg, _)) = tb.selection_range() {
2520 x = beg.visual_pos.x;
2521 y = beg.visual_pos.y - 1;
2522 tc.preferred_column = x;
2523 }
2524
2525 if y < 0 {
2528 x = 0;
2529 tc.preferred_column = 0;
2530 }
2531
2532 tb.cursor_move_to_visual(Point { x, y });
2533 }
2534 kbmod::CTRL => {
2535 tc.scroll_offset.y -= 1;
2536 make_cursor_visible = false;
2537 }
2538 kbmod::SHIFT => {
2539 if tb.cursor_visual_pos().y == 0 {
2542 tc.preferred_column = 0;
2543 }
2544
2545 tb.selection_update_visual(Point {
2546 x: tc.preferred_column,
2547 y: tb.cursor_visual_pos().y - 1,
2548 });
2549 }
2550 kbmod::CTRL_ALT => {
2551 }
2553 _ => return false,
2554 }
2555 }
2556 vk::RIGHT => {
2557 let granularity = if modifiers.contains(KBMOD_FOR_WORD_NAV) {
2558 CursorMovement::Word
2559 } else {
2560 CursorMovement::Grapheme
2561 };
2562 if modifiers.contains(kbmod::SHIFT) {
2563 tb.selection_update_delta(granularity, 1);
2564 } else if let Some((_, end)) = tb.selection_range() {
2565 unsafe { tb.set_cursor(end) };
2566 } else {
2567 tb.cursor_move_delta(granularity, 1);
2568 }
2569 }
2570 vk::DOWN => {
2571 if single_line {
2572 return false;
2573 }
2574 match modifiers {
2575 kbmod::NONE => {
2576 let mut x = tc.preferred_column;
2577 let mut y = tb.cursor_visual_pos().y + 1;
2578
2579 if let Some((_, end)) = tb.selection_range() {
2581 x = end.visual_pos.x;
2582 y = end.visual_pos.y + 1;
2583 tc.preferred_column = x;
2584 }
2585
2586 if y >= tb.visual_line_count() {
2589 x = CoordType::MAX;
2590 }
2591
2592 tb.cursor_move_to_visual(Point { x, y });
2593
2594 if x == CoordType::MAX {
2597 tc.preferred_column = tb.cursor_visual_pos().x;
2598 }
2599 }
2600 kbmod::CTRL => {
2601 tc.scroll_offset.y += 1;
2602 make_cursor_visible = false;
2603 }
2604 kbmod::SHIFT => {
2605 if tb.cursor_visual_pos().y >= tb.visual_line_count() - 1 {
2608 tc.preferred_column = CoordType::MAX;
2609 }
2610
2611 tb.selection_update_visual(Point {
2612 x: tc.preferred_column,
2613 y: tb.cursor_visual_pos().y + 1,
2614 });
2615
2616 if tc.preferred_column == CoordType::MAX {
2617 tc.preferred_column = tb.cursor_visual_pos().x;
2618 }
2619 }
2620 kbmod::CTRL_ALT => {
2621 }
2623 _ => return false,
2624 }
2625 }
2626 vk::INSERT => match modifiers {
2627 kbmod::SHIFT => tb.paste(self.clipboard_ref()),
2628 kbmod::CTRL => tb.copy(self.clipboard_mut()),
2629 _ => tb.set_overtype(!tb.is_overtype()),
2630 },
2631 vk::DELETE => match modifiers {
2632 kbmod::SHIFT => tb.cut(self.clipboard_mut()),
2633 kbmod::CTRL => tb.delete(CursorMovement::Word, 1),
2634 _ => tb.delete(CursorMovement::Grapheme, 1),
2635 },
2636 vk::A => match modifiers {
2637 kbmod::CTRL => tb.select_all(),
2638 _ => return false,
2639 },
2640 vk::B => match modifiers {
2641 kbmod::ALT if cfg!(target_os = "macos") => {
2642 tb.cursor_move_delta(CursorMovement::Word, -1);
2645 }
2646 _ => return false,
2647 },
2648 vk::F => match modifiers {
2649 kbmod::ALT if cfg!(target_os = "macos") => {
2650 tb.cursor_move_delta(CursorMovement::Word, 1);
2653 }
2654 _ => return false,
2655 },
2656 vk::H => match modifiers {
2657 kbmod::CTRL => tb.delete(CursorMovement::Word, -1),
2658 _ => return false,
2659 },
2660 vk::X => match modifiers {
2661 kbmod::CTRL => tb.cut(self.clipboard_mut()),
2662 _ => return false,
2663 },
2664 vk::C => match modifiers {
2665 kbmod::CTRL => tb.copy(self.clipboard_mut()),
2666 _ => return false,
2667 },
2668 vk::V => match modifiers {
2669 kbmod::CTRL => tb.paste(self.clipboard_ref()),
2670 _ => return false,
2671 },
2672 vk::Y => match modifiers {
2673 kbmod::CTRL => tb.redo(),
2674 _ => return false,
2675 },
2676 vk::Z => match modifiers {
2677 kbmod::CTRL => tb.undo(),
2678 kbmod::CTRL_SHIFT => tb.redo(),
2679 kbmod::ALT => tb.set_word_wrap(!tb.is_word_wrap_enabled()),
2680 _ => return false,
2681 },
2682 _ => return false,
2683 }
2684
2685 change_preferred_column = !matches!(key, vk::PRIOR | vk::NEXT | vk::UP | vk::DOWN);
2686 } else {
2687 return false;
2688 }
2689
2690 if single_line && !write.is_empty() {
2691 let (end, _) = simd::lines_fwd(write, 0, 0, 1);
2692 write = unicode::strip_newline(&write[..end]);
2693 }
2694 if !write.is_empty() {
2695 tb.write_canon(write);
2696 change_preferred_column = true;
2697 make_cursor_visible = true;
2698 }
2699
2700 if change_preferred_column {
2701 tc.preferred_column = tb.cursor_visual_pos().x;
2702 }
2703
2704 self.set_input_consumed();
2705 make_cursor_visible
2706 }
2707
2708 fn textarea_make_cursor_visible(&self, tc: &mut TextareaContent, node_prev: &Node) {
2709 let tb = tc.buffer.borrow();
2710 let mut scroll_x = tc.scroll_offset.x;
2711 let mut scroll_y = tc.scroll_offset.y;
2712
2713 let text_width = tb.text_width();
2714 let cursor_x = tb.cursor_visual_pos().x;
2715 scroll_x = scroll_x.min(cursor_x - 10);
2716 scroll_x = scroll_x.max(cursor_x - text_width + 10);
2717
2718 let viewport_height = node_prev.inner.height();
2719 let cursor_y = tb.cursor_visual_pos().y;
2720 scroll_y = scroll_y.min(cursor_y);
2722 scroll_y = scroll_y.max(cursor_y - viewport_height + 1);
2724
2725 tc.scroll_offset.x = scroll_x;
2726 tc.scroll_offset.y = scroll_y;
2727 }
2728
2729 fn textarea_adjust_scroll_offset(&self, tc: &mut TextareaContent) {
2730 let tb = tc.buffer.borrow();
2731 let mut scroll_x = tc.scroll_offset.x;
2732 let mut scroll_y = tc.scroll_offset.y;
2733
2734 scroll_x = scroll_x.min(tc.scroll_offset_x_max.max(tb.cursor_visual_pos().x) - 10);
2735 scroll_x = scroll_x.max(0);
2736 scroll_y = scroll_y.clamp(0, tb.visual_line_count() - 1);
2737
2738 if tb.is_word_wrap_enabled() {
2739 scroll_x = 0;
2740 }
2741
2742 tc.scroll_offset.x = scroll_x;
2743 tc.scroll_offset.y = scroll_y;
2744 }
2745
2746 pub fn scrollarea_begin(&mut self, classname: &'static str, intrinsic_size: Size) {
2748 self.block_begin(classname);
2749
2750 let container_node = self.tree.last_node;
2751 {
2752 let mut container = self.tree.last_node.borrow_mut();
2753 container.content = NodeContent::Scrollarea(ScrollareaContent {
2754 scroll_offset: Point::MIN,
2755 scroll_offset_y_drag_start: CoordType::MIN,
2756 thumb_height: 0,
2757 });
2758
2759 if intrinsic_size.width > 0 || intrinsic_size.height > 0 {
2760 container.intrinsic_size.width = intrinsic_size.width.max(0);
2761 container.intrinsic_size.height = intrinsic_size.height.max(0);
2762 container.intrinsic_size_set = true;
2763 }
2764 }
2765
2766 self.block_begin("content");
2767 self.inherit_focus();
2768
2769 self.tree.last_node = container_node;
2771 }
2772
2773 pub fn scrollarea_scroll_to(&mut self, pos: Point) {
2775 let mut container = self.tree.last_node.borrow_mut();
2776 if let NodeContent::Scrollarea(sc) = &mut container.content {
2777 sc.scroll_offset = pos;
2778 } else {
2779 debug_assert!(false);
2780 }
2781 }
2782
2783 pub fn scrollarea_end(&mut self) {
2785 self.block_end(); self.block_end(); let mut container = self.tree.last_node.borrow_mut();
2789 let container_id = container.id;
2790 let container_depth = container.depth;
2791 let Some(prev_container) = self.tui.prev_node_map.get(container_id) else {
2792 return;
2793 };
2794
2795 let prev_container = prev_container.borrow();
2796 let NodeContent::Scrollarea(sc) = &mut container.content else {
2797 unreachable!();
2798 };
2799
2800 if sc.scroll_offset == Point::MIN
2801 && let NodeContent::Scrollarea(sc_prev) = &prev_container.content
2802 {
2803 *sc = sc_prev.clone();
2804 }
2805
2806 if !self.input_consumed {
2807 if self.tui.mouse_state != InputMouseState::None {
2808 let container_rect = prev_container.inner;
2809
2810 match self.tui.mouse_state {
2811 InputMouseState::Left => {
2812 if self.tui.mouse_is_drag {
2813 let track_rect = Rect {
2816 left: container_rect.right,
2817 top: container_rect.top,
2818 right: container_rect.right + 1,
2819 bottom: container_rect.bottom,
2820 };
2821 if track_rect.contains(self.tui.mouse_down_position) {
2822 if sc.scroll_offset_y_drag_start == CoordType::MIN {
2823 sc.scroll_offset_y_drag_start = sc.scroll_offset.y;
2824 }
2825
2826 let content = prev_container.children.first.unwrap().borrow();
2827 let content_rect = content.inner;
2828 let content_height = content_rect.height();
2829 let track_height = track_rect.height();
2830 let scrollable_height = content_height - track_height;
2831
2832 if scrollable_height > 0 {
2833 let trackable = track_height - sc.thumb_height;
2834 let delta_y =
2835 self.tui.mouse_position.y - self.tui.mouse_down_position.y;
2836 sc.scroll_offset.y = sc.scroll_offset_y_drag_start
2837 + (delta_y as i64 * scrollable_height as i64
2838 / trackable as i64)
2839 as CoordType;
2840 }
2841
2842 self.set_input_consumed();
2843 }
2844 }
2845 }
2846 InputMouseState::Release => {
2847 sc.scroll_offset_y_drag_start = CoordType::MIN;
2848 }
2849 InputMouseState::Scroll => {
2850 if container_rect.contains(self.tui.mouse_position) {
2851 sc.scroll_offset.x += self.input_scroll_delta.x;
2852 sc.scroll_offset.y += self.input_scroll_delta.y;
2853 self.set_input_consumed();
2854 }
2855 }
2856 _ => {}
2857 }
2858 } else if self.tui.is_subtree_focused_alt(container_id, container_depth)
2859 && let Some(key) = self.input_keyboard
2860 {
2861 match key {
2862 vk::PRIOR => sc.scroll_offset.y -= prev_container.inner_clipped.height(),
2863 vk::NEXT => sc.scroll_offset.y += prev_container.inner_clipped.height(),
2864 vk::END => sc.scroll_offset.y = CoordType::MAX,
2865 vk::HOME => sc.scroll_offset.y = 0,
2866 _ => return,
2867 }
2868 self.set_input_consumed();
2869 }
2870 }
2871 }
2872
2873 pub fn list_begin(&mut self, classname: &'static str) {
2875 self.block_begin(classname);
2876 self.attr_focusable();
2877
2878 let mut last_node = self.tree.last_node.borrow_mut();
2879 let content = self
2880 .tui
2881 .prev_node_map
2882 .get(last_node.id)
2883 .and_then(|node| match &node.borrow().content {
2884 NodeContent::List(content) => {
2885 Some(ListContent { selected: content.selected, selected_node: None })
2886 }
2887 _ => None,
2888 })
2889 .unwrap_or(ListContent { selected: 0, selected_node: None });
2890
2891 last_node.attributes.focus_void = true;
2892 last_node.content = NodeContent::List(content);
2893 }
2894
2895 pub fn list_item(&mut self, select: bool, text: &str) -> ListSelection {
2897 self.styled_list_item_begin();
2898 self.styled_label_add_text(text);
2899 self.styled_list_item_end(select)
2900 }
2901
2902 pub fn styled_list_item_begin(&mut self) {
2905 let list = self.tree.current_node;
2906 let idx = list.borrow().child_count;
2907
2908 self.next_block_id_mixin(idx as u64);
2909 self.styled_label_begin("item");
2910 self.styled_label_add_text(" ");
2911 self.attr_focusable();
2912 }
2913
2914 pub fn styled_list_item_end(&mut self, select: bool) -> ListSelection {
2916 self.styled_label_end();
2917
2918 let list = self.tree.current_node;
2919
2920 let selected_before;
2921 let selected_now;
2922 let focused;
2923 {
2924 let mut list = list.borrow_mut();
2925 let content = match &mut list.content {
2926 NodeContent::List(content) => content,
2927 _ => unreachable!(),
2928 };
2929
2930 let item = self.tree.last_node.borrow();
2931 let item_id = item.id;
2932 selected_before = content.selected == item_id;
2933 focused = self.is_focused();
2934
2935 selected_now = selected_before || (select && content.selected == 0) || focused;
2937
2938 if selected_now {
2940 content.selected_node = Some(self.tree.last_node);
2941 if !selected_before {
2942 content.selected = item_id;
2943 self.needs_rerender();
2944 }
2945 }
2946 }
2947
2948 let clicked =
2950 !self.input_consumed && (self.input_mouse_click == 2 && self.was_mouse_down());
2951 let entered = focused
2953 && selected_before
2954 && !self.input_consumed
2955 && matches!(self.input_keyboard, Some(vk::RETURN));
2956 let activated = clicked || entered;
2957 if activated {
2958 self.set_input_consumed();
2959 }
2960
2961 if selected_before && activated {
2962 ListSelection::Activated
2963 } else if selected_now && !selected_before {
2964 ListSelection::Selected
2965 } else {
2966 ListSelection::Unchanged
2967 }
2968 }
2969
2970 pub fn list_item_steal_focus(&mut self) {
2976 self.steal_focus();
2977
2978 match &mut self.tree.current_node.borrow_mut().content {
2979 NodeContent::List(content) => {
2980 content.selected = self.tree.last_node.borrow().id;
2981 content.selected_node = Some(self.tree.last_node);
2982 }
2983 _ => unreachable!(),
2984 }
2985 }
2986
2987 pub fn list_end(&mut self) {
2989 self.block_end();
2990
2991 let contains_focus;
2992 let selected_now;
2993 let mut selected_next;
2994 {
2995 let list = self.tree.last_node.borrow();
2996
2997 contains_focus = self.tui.is_subtree_focused(&list);
2998 selected_now = match &list.content {
2999 NodeContent::List(content) => content.selected_node,
3000 _ => unreachable!(),
3001 };
3002 selected_next = match selected_now.or(list.children.first) {
3003 Some(node) => node,
3004 None => return,
3005 };
3006 }
3007
3008 if contains_focus
3009 && !self.input_consumed
3010 && let Some(key) = self.input_keyboard
3011 && let Some(selected_now) = selected_now
3012 {
3013 let list = self.tree.last_node.borrow();
3014
3015 if let Some(prev_container) = self.tui.prev_node_map.get(list.id) {
3016 let mut consumed = true;
3017
3018 match key {
3019 vk::PRIOR => {
3020 selected_next = selected_now;
3021 for _ in 0..prev_container.borrow().inner_clipped.height() - 1 {
3022 let node = selected_next.borrow();
3023 selected_next = match node.siblings.prev {
3024 Some(node) => node,
3025 None => break,
3026 };
3027 }
3028 }
3029 vk::NEXT => {
3030 selected_next = selected_now;
3031 for _ in 0..prev_container.borrow().inner_clipped.height() - 1 {
3032 let node = selected_next.borrow();
3033 selected_next = match node.siblings.next {
3034 Some(node) => node,
3035 None => break,
3036 };
3037 }
3038 }
3039 vk::END => {
3040 selected_next = list.children.last.unwrap_or(selected_next);
3041 }
3042 vk::HOME => {
3043 selected_next = list.children.first.unwrap_or(selected_next);
3044 }
3045 vk::UP => {
3046 selected_next = selected_now
3047 .borrow()
3048 .siblings
3049 .prev
3050 .or(list.children.last)
3051 .unwrap_or(selected_next);
3052 }
3053 vk::DOWN => {
3054 selected_next = selected_now
3055 .borrow()
3056 .siblings
3057 .next
3058 .or(list.children.first)
3059 .unwrap_or(selected_next);
3060 }
3061 _ => consumed = false,
3062 }
3063
3064 if consumed {
3065 self.set_input_consumed();
3066 }
3067 }
3068 }
3069
3070 if !opt_ptr_eq(selected_now, Some(selected_next))
3072 && let NodeContent::List(content) = &mut self.tree.last_node.borrow_mut().content
3073 {
3074 content.selected_node = Some(selected_next);
3075 }
3076
3077 if let NodeContent::Text(content) = &mut selected_next.borrow_mut().content {
3079 unsafe {
3080 content.text.as_bytes_mut()[0] = b'>';
3081 }
3082 }
3083
3084 if contains_focus {
3086 {
3087 let mut node = selected_next.borrow_mut();
3088 node.attributes.bg = self.indexed(IndexedColor::Green);
3089 node.attributes.fg = self.contrasted(self.indexed(IndexedColor::Green));
3090 }
3091 self.steal_focus_for(selected_next);
3092 }
3093 }
3094
3095 pub fn menubar_begin(&mut self) {
3097 self.table_begin("menubar");
3098 self.attr_focus_well();
3099 self.table_next_row();
3100 }
3101
3102 pub fn menubar_menu_begin(&mut self, text: &str, accelerator: char) -> bool {
3106 let accelerator = if cfg!(target_os = "macos") { '\0' } else { accelerator };
3107 let mixin = self.tree.current_node.borrow().child_count as u64;
3108 self.next_block_id_mixin(mixin);
3109
3110 self.button_label(
3111 "menu_button",
3112 text,
3113 ButtonStyle::default().accelerator(accelerator).bracketed(false),
3114 );
3115 self.attr_focusable();
3116 self.attr_padding(Rect::two(0, 1));
3117
3118 let contains_focus = self.contains_focus();
3119 let keyboard_focus = accelerator != '\0'
3120 && !contains_focus
3121 && self.consume_shortcut(kbmod::ALT | InputKey::new(accelerator as u32));
3122
3123 if contains_focus || keyboard_focus {
3124 self.attr_background_rgba(self.tui.floater_default_bg);
3125 self.attr_foreground_rgba(self.tui.floater_default_fg);
3126
3127 if self.is_focused() {
3128 self.attr_background_rgba(self.indexed(IndexedColor::Green));
3129 self.attr_foreground_rgba(self.contrasted(self.indexed(IndexedColor::Green)));
3130 }
3131
3132 self.next_block_id_mixin(mixin);
3133 self.table_begin("flyout");
3134 self.attr_float(FloatSpec {
3135 anchor: Anchor::Last,
3136 gravity_x: 0.0,
3137 gravity_y: 0.0,
3138 offset_x: 0.0,
3139 offset_y: 1.0,
3140 });
3141 self.attr_border();
3142 self.attr_focus_well();
3143
3144 if keyboard_focus {
3145 self.steal_focus();
3146 }
3147
3148 true
3149 } else {
3150 false
3151 }
3152 }
3153
3154 pub fn menubar_menu_button(
3156 &mut self,
3157 text: &str,
3158 accelerator: char,
3159 shortcut: InputKey,
3160 ) -> bool {
3161 self.menubar_menu_checkbox(text, accelerator, shortcut, false)
3162 }
3163
3164 pub fn menubar_menu_checkbox(
3167 &mut self,
3168 text: &str,
3169 accelerator: char,
3170 shortcut: InputKey,
3171 checked: bool,
3172 ) -> bool {
3173 self.table_next_row();
3174 self.attr_focusable();
3175
3176 if self.tree.current_node.borrow_mut().siblings.prev.is_none() {
3178 self.inherit_focus();
3179 }
3180
3181 if self.is_focused() {
3182 self.attr_background_rgba(self.indexed(IndexedColor::Green));
3183 self.attr_foreground_rgba(self.contrasted(self.indexed(IndexedColor::Green)));
3184 }
3185
3186 let clicked =
3187 self.button_activated() || self.consume_shortcut(InputKey::new(accelerator as u32));
3188
3189 self.button_label(
3190 "menu_checkbox",
3191 text,
3192 ButtonStyle::default().bracketed(false).checked(checked).accelerator(accelerator),
3193 );
3194 self.menubar_shortcut(shortcut);
3195
3196 if clicked {
3197 self.needs_rerender();
3199 Tui::clean_node_path(&mut self.tui.focused_node_path);
3200 }
3201
3202 clicked
3203 }
3204
3205 pub fn menubar_menu_end(&mut self) {
3207 self.table_end();
3208
3209 if !self.input_consumed
3210 && let Some(key) = self.input_keyboard
3211 && matches!(key, vk::ESCAPE | vk::UP | vk::DOWN)
3212 {
3213 if matches!(key, vk::UP | vk::DOWN) {
3214 let ln = self.tree.last_node.borrow();
3217 if self.tui.is_node_focused(ln.parent.map_or(0, |n| n.borrow().id)) {
3218 let selected_next =
3219 if key == vk::UP { ln.children.last } else { ln.children.first };
3220 if let Some(selected_next) = selected_next {
3221 self.steal_focus_for(selected_next);
3222 self.set_input_consumed();
3223 }
3224 }
3225 } else if self.contains_focus() {
3226 self.tui.pop_focusable_node(1);
3229 }
3230 }
3231 }
3232
3233 pub fn menubar_end(&mut self) {
3235 self.table_end();
3236 }
3237
3238 fn button_label(&mut self, classname: &'static str, text: &str, style: ButtonStyle) {
3241 self.styled_label_begin(classname);
3243 if style.bracketed {
3244 self.styled_label_add_text("[");
3245 }
3246 if let Some(checked) = style.checked {
3247 self.styled_label_add_text(if checked { "🗹 " } else { " " });
3248 }
3249 match style.accelerator {
3251 Some(accelerator) if accelerator.is_ascii_uppercase() => {
3252 let mut off = text.len();
3255 for (i, c) in text.bytes().enumerate() {
3256 if c as char == accelerator {
3258 off = i;
3259 break;
3260 }
3261 if (c & !0x20) as char == accelerator && off == text.len() {
3263 off = i;
3264 }
3265 }
3266
3267 if off < text.len() {
3268 self.styled_label_add_text(&text[..off]);
3270 self.styled_label_set_attributes(Attributes::Underlined);
3271 self.styled_label_add_text(&text[off..off + 1]);
3272 self.styled_label_set_attributes(Attributes::None);
3273 self.styled_label_add_text(&text[off + 1..]);
3274 } else {
3275 let ch = accelerator as u8;
3277 self.styled_label_add_text(text);
3278 self.styled_label_add_text("(");
3279 self.styled_label_set_attributes(Attributes::Underlined);
3280 self.styled_label_add_text(unsafe { str_from_raw_parts(&ch, 1) });
3281 self.styled_label_set_attributes(Attributes::None);
3282 self.styled_label_add_text(")");
3283 }
3284 }
3285 _ => {
3286 self.styled_label_add_text(text);
3289 }
3290 }
3291 if style.bracketed {
3293 self.styled_label_add_text("]");
3294 }
3295 self.styled_label_end();
3296 }
3297
3298 fn menubar_shortcut(&mut self, shortcut: InputKey) {
3299 let shortcut_letter = shortcut.value() as u8 as char;
3300 if shortcut_letter.is_ascii_uppercase() {
3301 let mut shortcut_text = ArenaString::new_in(self.arena());
3302 if shortcut.modifiers_contains(kbmod::CTRL) {
3303 shortcut_text.push_str(self.tui.modifier_translations.ctrl);
3304 shortcut_text.push('+');
3305 }
3306 if shortcut.modifiers_contains(kbmod::ALT) {
3307 shortcut_text.push_str(self.tui.modifier_translations.alt);
3308 shortcut_text.push('+');
3309 }
3310 if shortcut.modifiers_contains(kbmod::SHIFT) {
3311 shortcut_text.push_str(self.tui.modifier_translations.shift);
3312 shortcut_text.push('+');
3313 }
3314 shortcut_text.push(shortcut_letter);
3315
3316 self.label("shortcut", &shortcut_text);
3317 } else {
3318 self.block_begin("shortcut");
3319 self.block_end();
3320 }
3321 self.attr_padding(Rect { left: 2, top: 0, right: 2, bottom: 0 });
3322 }
3323}
3324
3325#[derive(Clone, Copy)]
3327enum VisitControl {
3328 Continue,
3329 SkipChildren,
3330 Stop,
3331}
3332
3333struct Tree<'a> {
3335 tail: &'a NodeCell<'a>,
3336 root_first: &'a NodeCell<'a>,
3337 root_last: &'a NodeCell<'a>,
3338 last_node: &'a NodeCell<'a>,
3339 current_node: &'a NodeCell<'a>,
3340
3341 count: usize,
3342 checksum: u64,
3343}
3344
3345impl<'a> Tree<'a> {
3346 fn new(arena: &'a Arena) -> Self {
3349 let root = Self::alloc_node(arena);
3350 {
3351 let mut r = root.borrow_mut();
3352 r.id = ROOT_ID;
3353 r.classname = "root";
3354 r.attributes.focusable = true;
3355 r.attributes.focus_well = true;
3356 }
3357 Self {
3358 tail: root,
3359 root_first: root,
3360 root_last: root,
3361 last_node: root,
3362 current_node: root,
3363 count: 1,
3364 checksum: ROOT_ID,
3365 }
3366 }
3367
3368 fn alloc_node(arena: &'a Arena) -> &'a NodeCell<'a> {
3369 arena.alloc_uninit().write(Default::default())
3370 }
3371
3372 fn push_child(&mut self, node: &'a NodeCell<'a>) {
3374 let mut n = node.borrow_mut();
3375 n.parent = Some(self.current_node);
3376 n.stack_parent = Some(self.current_node);
3377
3378 {
3379 let mut p = self.current_node.borrow_mut();
3380 n.siblings.prev = p.children.last;
3381 n.depth = p.depth + 1;
3382
3383 if let Some(child_last) = p.children.last {
3384 let mut child_last = child_last.borrow_mut();
3385 child_last.siblings.next = Some(node);
3386 }
3387 if p.children.first.is_none() {
3388 p.children.first = Some(node);
3389 }
3390 p.children.last = Some(node);
3391 p.child_count += 1;
3392 }
3393
3394 n.prev = Some(self.tail);
3395 {
3396 let mut tail = self.tail.borrow_mut();
3397 tail.next = Some(node);
3398 }
3399 self.tail = node;
3400
3401 self.last_node = node;
3402 self.current_node = node;
3403 self.count += 1;
3404 self.checksum = wymix(self.checksum, n.id);
3406 }
3407
3408 fn move_node_to_root(&mut self, node: &'a NodeCell<'a>, anchor: Option<&'a NodeCell<'a>>) {
3411 let mut n = node.borrow_mut();
3412 let Some(parent) = n.parent else {
3413 return;
3414 };
3415
3416 if let Some(sibling_prev) = n.siblings.prev {
3417 let mut sibling_prev = sibling_prev.borrow_mut();
3418 sibling_prev.siblings.next = n.siblings.next;
3419 }
3420 if let Some(sibling_next) = n.siblings.next {
3421 let mut sibling_next = sibling_next.borrow_mut();
3422 sibling_next.siblings.prev = n.siblings.prev;
3423 }
3424
3425 {
3426 let mut p = parent.borrow_mut();
3427 if opt_ptr_eq(p.children.first, Some(node)) {
3428 p.children.first = n.siblings.next;
3429 }
3430 if opt_ptr_eq(p.children.last, Some(node)) {
3431 p.children.last = n.siblings.prev;
3432 }
3433 p.child_count -= 1;
3434 }
3435
3436 n.parent = anchor;
3437 n.depth = anchor.map_or(0, |n| n.borrow().depth + 1);
3438 n.siblings.prev = Some(self.root_last);
3439 n.siblings.next = None;
3440
3441 self.root_last.borrow_mut().siblings.next = Some(node);
3442 self.root_last = node;
3443 }
3444
3445 fn pop_stack(&mut self) {
3447 let current_node = self.current_node.borrow();
3448 if let Some(stack_parent) = current_node.stack_parent {
3449 self.last_node = self.current_node;
3450 self.current_node = stack_parent;
3451 }
3452 }
3453
3454 fn iterate_siblings(
3455 mut node: Option<&'a NodeCell<'a>>,
3456 ) -> impl Iterator<Item = &'a NodeCell<'a>> + use<'a> {
3457 iter::from_fn(move || {
3458 let n = node?;
3459 node = n.borrow().siblings.next;
3460 Some(n)
3461 })
3462 }
3463
3464 fn iterate_siblings_rev(
3465 mut node: Option<&'a NodeCell<'a>>,
3466 ) -> impl Iterator<Item = &'a NodeCell<'a>> + use<'a> {
3467 iter::from_fn(move || {
3468 let n = node?;
3469 node = n.borrow().siblings.prev;
3470 Some(n)
3471 })
3472 }
3473
3474 fn iterate_roots(&self) -> impl Iterator<Item = &'a NodeCell<'a>> + use<'a> {
3475 Self::iterate_siblings(Some(self.root_first))
3476 }
3477
3478 fn iterate_roots_rev(&self) -> impl Iterator<Item = &'a NodeCell<'a>> + use<'a> {
3479 Self::iterate_siblings_rev(Some(self.root_last))
3480 }
3481
3482 fn visit_all<T: FnMut(&'a NodeCell<'a>) -> VisitControl>(
3487 root: &'a NodeCell<'a>,
3488 start: &'a NodeCell<'a>,
3489 forward: bool,
3490 mut cb: T,
3491 ) {
3492 let root_depth = root.borrow().depth;
3493 let mut node = start;
3494 let children_idx = if forward { NodeChildren::FIRST } else { NodeChildren::LAST };
3495 let siblings_idx = if forward { NodeSiblings::NEXT } else { NodeSiblings::PREV };
3496
3497 while {
3498 'traverse: {
3499 match cb(node) {
3500 VisitControl::Continue => {
3501 if let Some(child) = node.borrow().children.get(children_idx) {
3503 node = child;
3504 break 'traverse;
3505 }
3506 }
3507 VisitControl::SkipChildren => {}
3508 VisitControl::Stop => return,
3509 }
3510
3511 loop {
3512 let n = node.borrow();
3515 if n.depth <= root_depth {
3516 break 'traverse;
3517 }
3518
3519 if let Some(sibling) = n.siblings.get(siblings_idx) {
3521 node = sibling;
3522 break;
3523 }
3524
3525 node = n.parent.unwrap();
3527 }
3528 }
3529
3530 !ptr::eq(node, start)
3532 } {}
3533 }
3534}
3535
3536struct NodeMap<'a> {
3541 slots: &'a [Option<&'a NodeCell<'a>>],
3542 shift: usize,
3543 mask: u64,
3544}
3545
3546impl Default for NodeMap<'static> {
3547 fn default() -> Self {
3548 Self { slots: &[None, None], shift: 63, mask: 0 }
3549 }
3550}
3551
3552impl<'a> NodeMap<'a> {
3553 fn new(arena: &'a Arena, tree: &Tree<'a>) -> Self {
3555 let width = (4 * tree.count + 1).ilog2().max(1) as usize;
3558 let slots = 1 << width;
3559 let shift = 64 - width;
3560 let mask = (slots - 1) as u64;
3561
3562 let slots = arena.alloc_uninit_slice(slots).write_filled(None);
3563 let mut node = tree.root_first;
3564
3565 loop {
3566 let n = node.borrow();
3567 let mut slot = n.id >> shift;
3568
3569 loop {
3570 if slots[slot as usize].is_none() {
3571 slots[slot as usize] = Some(node);
3572 break;
3573 }
3574 slot = (slot + 1) & mask;
3575 }
3576
3577 node = match n.next {
3578 Some(node) => node,
3579 None => break,
3580 };
3581 }
3582
3583 Self { slots, shift, mask }
3584 }
3585
3586 fn get(&mut self, id: u64) -> Option<&'a NodeCell<'a>> {
3588 let shift = self.shift;
3589 let mask = self.mask;
3590 let mut slot = id >> shift;
3591
3592 loop {
3593 let node = self.slots[slot as usize]?;
3594 if node.borrow().id == id {
3595 return Some(node);
3596 }
3597 slot = (slot + 1) & mask;
3598 }
3599 }
3600}
3601
3602struct FloatAttributes {
3603 gravity_x: f32,
3605 gravity_y: f32,
3606 offset_x: f32,
3608 offset_y: f32,
3609}
3610
3611#[derive(Default)]
3613struct NodeAttributes {
3614 float: Option<FloatAttributes>,
3615 position: Position,
3616 padding: Rect,
3617 bg: u32,
3618 fg: u32,
3619 reverse: bool,
3620 bordered: bool,
3621 focusable: bool,
3622 focus_well: bool, focus_void: bool, }
3625
3626struct ListContent<'a> {
3628 selected: u64,
3629 selected_node: Option<&'a NodeCell<'a>>,
3631}
3632
3633struct TableContent<'a> {
3635 columns: Vec<CoordType, &'a Arena>,
3636 cell_gap: Size,
3637}
3638
3639struct StyledTextChunk {
3641 offset: usize,
3642 fg: u32,
3643 attr: Attributes,
3644}
3645
3646const INVALID_STYLED_TEXT_CHUNK: StyledTextChunk =
3647 StyledTextChunk { offset: usize::MAX, fg: 0, attr: Attributes::None };
3648
3649struct TextContent<'a> {
3651 text: ArenaString<'a>,
3652 chunks: Vec<StyledTextChunk, &'a Arena>,
3653 overflow: Overflow,
3654}
3655
3656struct TextareaContent<'a> {
3658 buffer: &'a TextBufferCell,
3659
3660 scroll_offset: Point,
3662 scroll_offset_y_drag_start: CoordType,
3663 scroll_offset_x_max: CoordType,
3664 thumb_height: CoordType,
3665 preferred_column: CoordType,
3666
3667 single_line: bool,
3668 has_focus: bool,
3669}
3670
3671#[derive(Clone)]
3673struct ScrollareaContent {
3674 scroll_offset: Point,
3675 scroll_offset_y_drag_start: CoordType,
3676 thumb_height: CoordType,
3677}
3678
3679#[derive(Default)]
3681enum NodeContent<'a> {
3682 #[default]
3683 None,
3684 List(ListContent<'a>),
3685 Modal(ArenaString<'a>), Table(TableContent<'a>),
3687 Text(TextContent<'a>),
3688 Textarea(TextareaContent<'a>),
3689 Scrollarea(ScrollareaContent),
3690}
3691
3692#[derive(Default)]
3694struct NodeSiblings<'a> {
3695 prev: Option<&'a NodeCell<'a>>,
3696 next: Option<&'a NodeCell<'a>>,
3697}
3698
3699impl<'a> NodeSiblings<'a> {
3700 const PREV: usize = 0;
3701 const NEXT: usize = 1;
3702
3703 fn get(&self, off: usize) -> Option<&'a NodeCell<'a>> {
3704 match off & 1 {
3705 0 => self.prev,
3706 1 => self.next,
3707 _ => unreachable!(),
3708 }
3709 }
3710}
3711
3712#[derive(Default)]
3714struct NodeChildren<'a> {
3715 first: Option<&'a NodeCell<'a>>,
3716 last: Option<&'a NodeCell<'a>>,
3717}
3718
3719impl<'a> NodeChildren<'a> {
3720 const FIRST: usize = 0;
3721 const LAST: usize = 1;
3722
3723 fn get(&self, off: usize) -> Option<&'a NodeCell<'a>> {
3724 match off & 1 {
3725 0 => self.first,
3726 1 => self.last,
3727 _ => unreachable!(),
3728 }
3729 }
3730}
3731
3732type NodeCell<'a> = SemiRefCell<Node<'a>>;
3733
3734#[derive(Default)]
3738struct Node<'a> {
3739 prev: Option<&'a NodeCell<'a>>,
3740 next: Option<&'a NodeCell<'a>>,
3741 stack_parent: Option<&'a NodeCell<'a>>,
3742
3743 id: u64,
3744 classname: &'static str,
3745 parent: Option<&'a NodeCell<'a>>,
3746 depth: usize,
3747 siblings: NodeSiblings<'a>,
3748 children: NodeChildren<'a>,
3749 child_count: usize,
3750
3751 attributes: NodeAttributes,
3752 content: NodeContent<'a>,
3753
3754 intrinsic_size: Size,
3755 intrinsic_size_set: bool,
3756 outer: Rect, inner: Rect, outer_clipped: Rect, inner_clipped: Rect, }
3761
3762impl Node<'_> {
3763 fn outer_to_inner(&self, mut outer: Rect) -> Rect {
3766 let l = self.attributes.bordered;
3767 let t = self.attributes.bordered;
3768 let r = self.attributes.bordered || matches!(self.content, NodeContent::Scrollarea(..));
3769 let b = self.attributes.bordered;
3770
3771 outer.left += self.attributes.padding.left + l as CoordType;
3772 outer.top += self.attributes.padding.top + t as CoordType;
3773 outer.right -= self.attributes.padding.right + r as CoordType;
3774 outer.bottom -= self.attributes.padding.bottom + b as CoordType;
3775 outer
3776 }
3777
3778 fn intrinsic_to_outer(&self) -> Size {
3781 let l = self.attributes.bordered;
3782 let t = self.attributes.bordered;
3783 let r = self.attributes.bordered || matches!(self.content, NodeContent::Scrollarea(..));
3784 let b = self.attributes.bordered;
3785
3786 let mut size = self.intrinsic_size;
3787 size.width += self.attributes.padding.left
3788 + self.attributes.padding.right
3789 + l as CoordType
3790 + r as CoordType;
3791 size.height += self.attributes.padding.top
3792 + self.attributes.padding.bottom
3793 + t as CoordType
3794 + b as CoordType;
3795 size
3796 }
3797
3798 fn compute_intrinsic_size(&mut self) {
3800 match &mut self.content {
3801 NodeContent::Table(spec) => {
3802 for row in Tree::iterate_siblings(self.children.first) {
3804 let mut row = row.borrow_mut();
3805 let mut row_height = 0;
3806
3807 for (column, cell) in Tree::iterate_siblings(row.children.first).enumerate() {
3808 let mut cell = cell.borrow_mut();
3809 cell.compute_intrinsic_size();
3810
3811 let size = cell.intrinsic_to_outer();
3812
3813 if column >= spec.columns.len() {
3823 spec.columns.push(0);
3824 }
3825 spec.columns[column] = spec.columns[column].max(size.width);
3826
3827 row_height = row_height.max(size.height);
3828 }
3829
3830 row.intrinsic_size.height = row_height;
3831 }
3832
3833 let total_gap_width =
3836 spec.cell_gap.width * spec.columns.len().saturating_sub(1) as CoordType;
3837 let total_inner_width = spec.columns.iter().sum::<CoordType>() + total_gap_width;
3838 let mut total_width = 0;
3839 let mut total_height = 0;
3840
3841 for row in Tree::iterate_siblings(self.children.first) {
3843 let mut row = row.borrow_mut();
3844 row.intrinsic_size.width = total_inner_width;
3845 row.intrinsic_size_set = true;
3846
3847 let size = row.intrinsic_to_outer();
3848 total_width = total_width.max(size.width);
3849 total_height += size.height;
3850 }
3851
3852 let total_gap_height =
3853 spec.cell_gap.height * self.child_count.saturating_sub(1) as CoordType;
3854 total_height += total_gap_height;
3855
3856 if !self.intrinsic_size_set {
3858 self.intrinsic_size.width = total_width;
3859 self.intrinsic_size.height = total_height;
3860 self.intrinsic_size_set = true;
3861 }
3862 }
3863 _ => {
3864 let mut max_width = 0;
3865 let mut total_height = 0;
3866
3867 for child in Tree::iterate_siblings(self.children.first) {
3868 let mut child = child.borrow_mut();
3869 child.compute_intrinsic_size();
3870
3871 let size = child.intrinsic_to_outer();
3872 max_width = max_width.max(size.width);
3873 total_height += size.height;
3874 }
3875
3876 if !self.intrinsic_size_set {
3877 self.intrinsic_size.width = max_width;
3878 self.intrinsic_size.height = total_height;
3879 self.intrinsic_size_set = true;
3880 }
3881 }
3882 }
3883 }
3884
3885 fn layout_children(&mut self, clip: Rect) {
3888 if self.children.first.is_none() || self.inner.is_empty() {
3889 return;
3890 }
3891
3892 match &mut self.content {
3893 NodeContent::Table(spec) => {
3894 let width = self.inner.right - self.inner.left;
3895 let mut x = self.inner.left;
3896 let mut y = self.inner.top;
3897
3898 for row in Tree::iterate_siblings(self.children.first) {
3899 let mut row = row.borrow_mut();
3900 let mut size = row.intrinsic_to_outer();
3901 size.width = width;
3902 row.outer.left = x;
3903 row.outer.top = y;
3904 row.outer.right = x + size.width;
3905 row.outer.bottom = y + size.height;
3906 row.outer = row.outer.intersect(self.inner);
3907 row.inner = row.outer_to_inner(row.outer);
3908 row.outer_clipped = row.outer.intersect(clip);
3909 row.inner_clipped = row.inner.intersect(clip);
3910
3911 let mut row_height = 0;
3912
3913 for (column, cell) in Tree::iterate_siblings(row.children.first).enumerate() {
3914 let mut cell = cell.borrow_mut();
3915 let mut size = cell.intrinsic_to_outer();
3916 size.width = spec.columns[column];
3917 cell.outer.left = x;
3918 cell.outer.top = y;
3919 cell.outer.right = x + size.width;
3920 cell.outer.bottom = y + size.height;
3921 cell.outer = cell.outer.intersect(self.inner);
3922 cell.inner = cell.outer_to_inner(cell.outer);
3923 cell.outer_clipped = cell.outer.intersect(clip);
3924 cell.inner_clipped = cell.inner.intersect(clip);
3925
3926 x += size.width + spec.cell_gap.width;
3927 row_height = row_height.max(size.height);
3928
3929 cell.layout_children(clip);
3930 }
3931
3932 x = self.inner.left;
3933 y += row_height + spec.cell_gap.height;
3934 }
3935 }
3936 NodeContent::Scrollarea(sc) => {
3937 let mut content = self.children.first.unwrap().borrow_mut();
3938
3939 let sx = self.inner.right - self.inner.left;
3941 let sy = self.inner.bottom - self.inner.top;
3942 let cx = sx;
3944 let cy = content.intrinsic_size.height.max(sy);
3945 let ox = 0;
3947 let oy = sc.scroll_offset.y.clamp(0, cy - sy);
3948
3949 sc.scroll_offset.x = ox;
3950 sc.scroll_offset.y = oy;
3951
3952 content.outer.left = self.inner.left - ox;
3953 content.outer.top = self.inner.top - oy;
3954 content.outer.right = content.outer.left + cx;
3955 content.outer.bottom = content.outer.top + cy;
3956 content.inner = content.outer_to_inner(content.outer);
3957 content.outer_clipped = content.outer.intersect(self.inner_clipped);
3958 content.inner_clipped = content.inner.intersect(self.inner_clipped);
3959
3960 let clip = content.inner_clipped;
3961 content.layout_children(clip);
3962 }
3963 _ => {
3964 let width = self.inner.right - self.inner.left;
3965 let x = self.inner.left;
3966 let mut y = self.inner.top;
3967
3968 for child in Tree::iterate_siblings(self.children.first) {
3969 let mut child = child.borrow_mut();
3970 let size = child.intrinsic_to_outer();
3971 let remaining = (width - size.width).max(0);
3972
3973 child.outer.left = x + match child.attributes.position {
3974 Position::Stretch | Position::Left => 0,
3975 Position::Center => remaining / 2,
3976 Position::Right => remaining,
3977 };
3978 child.outer.right = child.outer.left
3979 + match child.attributes.position {
3980 Position::Stretch => width,
3981 _ => size.width,
3982 };
3983 child.outer.top = y;
3984 child.outer.bottom = y + size.height;
3985
3986 child.outer = child.outer.intersect(self.inner);
3987 child.inner = child.outer_to_inner(child.outer);
3988 child.outer_clipped = child.outer.intersect(clip);
3989 child.inner_clipped = child.inner.intersect(clip);
3990
3991 y += size.height;
3992 }
3993
3994 for child in Tree::iterate_siblings(self.children.first) {
3995 let mut child = child.borrow_mut();
3996 child.layout_children(clip);
3997 }
3998 }
3999 }
4000 }
4001}