edit/
tui.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! An immediate mode UI framework for terminals.
5//!
6//! # Why immediate mode?
7//!
8//! This uses an "immediate mode" design, similar to [ImGui](https://github.com/ocornut/imgui).
9//! The reason for this is that I expect the UI needs for any terminal application to be
10//! fairly minimal, and for that purpose an immediate mode design is much simpler to use.
11//!
12//! So what's "immediate mode"? The primary alternative is called "retained mode".
13//! The difference is that when you create a button in this framework in one frame,
14//! and you stop telling this framework in the next frame, the button will vanish.
15//! When you use a regular retained mode UI framework, you create the button once,
16//! set up callbacks for when it is clicked, and then stop worrying about it.
17//!
18//! The downside of immediate mode is that your UI code _may_ become cluttered.
19//! The upside however is that you cannot leak UI elements, you don't need to
20//! worry about lifetimes nor callbacks, and that simple UIs are simple to write.
21//!
22//! More importantly though, the primary reason for this is that the
23//! lack of callbacks means we can use this design across a plain C ABI,
24//! which we'll need once plugins come into play. GTK's `g_signal_connect`
25//! shows that the alternative can be rather cumbersome.
26//!
27//! # Design overview
28//!
29//! While this file is fairly lengthy, the overall algorithm is simple.
30//! On the first frame ever:
31//! * Prepare an empty `arena_next`.
32//! * Parse the incoming [`input::Input`] which should be a resize event.
33//! * Create a new [`Context`] instance and give it the caller.
34//! * Now the caller will draw their UI with the [`Context`] by calling the
35//!   various [`Context`] UI methods, such as [`Context::block_begin()`] and
36//!   [`Context::block_end()`]. These two are the basis which all other UI
37//!   elements are built upon by the way. Each UI element that is created gets
38//!   allocated onto `arena_next` and inserted into the UI tree.
39//!   That tree works exactly like the DOM tree in HTML: Each node in the tree
40//!   has a parent, children, and siblings. The tree layout at the end is then
41//!   a direct mirror of the code "layout" that created it.
42//! * Once the caller is done and drops the [`Context`], it'll secretly call
43//!   `report_context_completion`. This causes a number of things:
44//!   * The DOM tree that was built is stored in `prev_tree`.
45//!   * A hashmap of all nodes is built and stored in `prev_node_map`.
46//!   * `arena_next` is swapped with `arena_prev`.
47//!   * Each UI node is measured and laid out.
48//! * Now the caller is expected to repeat this process with a [`None`]
49//!   input event until [`Tui::needs_settling()`] returns false.
50//!   This is necessary, because when [`Context::button()`] returns `true`
51//!   in one frame, it may change the state in the caller's code
52//!   and require another frame to be drawn.
53//! * Finally a call to [`Tui::render()`] will render the UI tree into the
54//!   framebuffer and return VT output.
55//!
56//! On every subsequent frame the process is similar, but one crucial element
57//! of any immediate mode UI framework is added:
58//! Now when the caller draws their UI, the various [`Context`] UI elements
59//! have access to `prev_node_map` and the previously built UI tree.
60//! This allows the UI framework to reuse the previously computed layout for
61//! hit tests, caching scroll offsets, and so on.
62//!
63//! In the end it looks very similar:
64//! * Prepare an empty `arena_next`.
65//! * Parse the incoming [`input::Input`]...
66//!   * **BUT** now we can hit-test mouse clicks onto the previously built
67//!     UI tree. This way we can delegate focus on left mouse clicks.
68//! * Create a new [`Context`] instance and give it the caller.
69//! * The caller draws their UI with the [`Context`]...
70//!   * **BUT** we can preserve the UI state across frames.
71//! * Continue rendering until [`Tui::needs_settling()`] returns false.
72//! * And the final call to [`Tui::render()`].
73//!
74//! # Classnames and node IDs
75//!
76//! So how do we find which node from the previous tree correlates to the
77//! current node? Each node needs to be constructed with a "classname".
78//! The classname is hashed with the parent node ID as the seed. This derived
79//! hash is then used as the new child node ID. Under the assumption that the
80//! collision likelihood of the hash function is low, this serves as true IDs.
81//!
82//! This has the nice added property that finding a node with the same ID
83//! guarantees that all of the parent nodes must have equivalent IDs as well.
84//! This turns "is the focus anywhere inside this subtree" into an O(1) check.
85//!
86//! The reason "classnames" are used is because I was hoping to add theming
87//! in the future with a syntax similar to CSS (simplified, however).
88//!
89//! # Example
90//!
91//! ```
92//! use edit::helpers::Size;
93//! use edit::input::Input;
94//! use edit::tui::*;
95//! use edit::{arena, arena_format};
96//!
97//! struct State {
98//!     counter: i32,
99//! }
100//!
101//! fn main() {
102//!     arena::init(128 * 1024 * 1024).unwrap();
103//!
104//!     // Create a `Tui` instance which holds state across frames.
105//!     let mut tui = Tui::new().unwrap();
106//!     let mut state = State { counter: 0 };
107//!     let input = Input::Resize(Size { width: 80, height: 24 });
108//!
109//!     // Pass the input to the TUI.
110//!     {
111//!         let mut ctx = tui.create_context(Some(input));
112//!         draw(&mut ctx, &mut state);
113//!     }
114//!
115//!     // Continue until the layout has settled.
116//!     while tui.needs_settling() {
117//!         let mut ctx = tui.create_context(None);
118//!         draw(&mut ctx, &mut state);
119//!     }
120//!
121//!     // Render the output.
122//!     let scratch = arena::scratch_arena(None);
123//!     let output = tui.render(&*scratch);
124//!     println!("{}", output);
125//! }
126//!
127//! fn draw(ctx: &mut Context, state: &mut State) {
128//!     ctx.table_begin("classname");
129//!     {
130//!         ctx.table_next_row();
131//!
132//!         // Thanks to the lack of callbacks, we can use a primitive
133//!         // if condition here, as well as in any potential C code.
134//!         if ctx.button("button", "Click me!", ButtonStyle::default()) {
135//!             state.counter += 1;
136//!         }
137//!
138//!         // Similarly, formatting and showing labels is straightforward.
139//!         // It's impossible to forget updating the label this way.
140//!         ctx.label("label", &arena_format!(ctx.arena(), "Counter: {}", state.counter));
141//!     }
142//!     ctx.table_end();
143//! }
144//! ```
145
146use 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; // Knuth's MMIX constant
164const 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
172/// Since [`TextBuffer`] creation and management is expensive,
173/// we cache instances of them for reuse between frames.
174/// This is used for [`Context::editline()`].
175struct CachedTextBuffer {
176    node_id: u64,
177    editor: RcTextBuffer,
178    seen: bool,
179}
180
181/// Since [`Context::editline()`] and [`Context::textarea()`]
182/// do almost the same thing, this abstracts over the two.
183enum TextBufferPayload<'a> {
184    Editline(&'a mut dyn WriteableDocument),
185    Textarea(RcTextBuffer),
186}
187
188/// In order for the TUI to show the correct Ctrl/Alt/Shift
189/// translations, this struct lets you set them.
190pub struct ModifierTranslations {
191    pub ctrl: &'static str,
192    pub alt: &'static str,
193    pub shift: &'static str,
194}
195
196/// Controls to which node the floater is anchored.
197#[derive(Default, Clone, Copy, PartialEq, Eq)]
198pub enum Anchor {
199    /// The floater is attached relative to the node created last.
200    #[default]
201    Last,
202    /// The floater is attached relative to the current node (= parent of new nodes).
203    Parent,
204    /// The floater is attached relative to the root node (= usually the viewport).
205    Root,
206}
207
208/// Controls the position of the floater. See [`Context::attr_float`].
209#[derive(Default)]
210pub struct FloatSpec {
211    /// Controls to which node the floater is anchored.
212    pub anchor: Anchor,
213    // Specifies the origin of the container relative to the container size. [0, 1]
214    pub gravity_x: f32,
215    pub gravity_y: f32,
216    // Specifies an offset from the origin in cells.
217    pub offset_x: f32,
218    pub offset_y: f32,
219}
220
221/// Informs you about the change that was made to the list selection.
222#[derive(Clone, Copy, PartialEq, Eq)]
223pub enum ListSelection {
224    /// The selection wasn't changed.
225    Unchanged,
226    /// The selection was changed to the current list item.
227    Selected,
228    /// The selection was changed to the current list item
229    /// *and* the item was also activated (Enter or Double-click).
230    Activated,
231}
232
233/// Controls the position of a node relative to its parent.
234#[derive(Default)]
235pub enum Position {
236    /// The child is stretched to fill the parent.
237    #[default]
238    Stretch,
239    /// The child is positioned at the left edge of the parent.
240    Left,
241    /// The child is positioned at the center of the parent.
242    Center,
243    /// The child is positioned at the right edge of the parent.
244    Right,
245}
246
247/// Controls the text overflow behavior of a label
248/// when the text doesn't fit the container.
249#[derive(Default, Clone, Copy, PartialEq, Eq)]
250pub enum Overflow {
251    /// Text is simply cut off when it doesn't fit.
252    #[default]
253    Clip,
254    /// An ellipsis is shown at the end of the text.
255    TruncateHead,
256    /// An ellipsis is shown in the middle of the text.
257    TruncateMiddle,
258    /// An ellipsis is shown at the beginning of the text.
259    TruncateTail,
260}
261
262/// Controls the style with which a button label renders
263#[derive(Clone, Copy)]
264pub struct ButtonStyle {
265    accelerator: Option<char>,
266    checked: Option<bool>,
267    bracketed: bool,
268}
269
270impl ButtonStyle {
271    /// Draw an accelerator label: `[_E_xample button]` or `[Example button(X)]`
272    ///
273    /// Must provide an upper-case ASCII character.
274    pub fn accelerator(self, char: char) -> Self {
275        Self { accelerator: Some(char), ..self }
276    }
277    /// Draw a checkbox prefix: `[🗹 Example Button]`
278    pub fn checked(self, checked: bool) -> Self {
279        Self { checked: Some(checked), ..self }
280    }
281    /// Draw with or without brackets: `[Example Button]` or `Example Button`
282    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, // Default style for most buttons. Brackets may be disabled e.g. for buttons in menus
293        }
294    }
295}
296
297/// There's two types of lifetimes the TUI code needs to manage:
298/// * Across frames
299/// * Per frame
300///
301/// [`Tui`] manages the first one. It's also the entrypoint for
302/// everything else you may want to do.
303pub struct Tui {
304    /// Arena used for the previous frame.
305    arena_prev: Arena,
306    /// Arena used for the current frame.
307    arena_next: Arena,
308    /// The UI tree built in the previous frame.
309    /// This refers to memory in `arena_prev`.
310    prev_tree: Tree<'static>,
311    /// A hashmap of all nodes built in the previous frame.
312    /// This refers to memory in `arena_prev`.
313    prev_node_map: NodeMap<'static>,
314    /// The framebuffer used for rendering.
315    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    /// Last known terminal size.
324    ///
325    /// This lives here instead of [`Context`], because we need to
326    /// track the state across frames and input events.
327    /// This also applies to the remaining members in this block below.
328    size: Size,
329    /// Last known mouse position.
330    mouse_position: Point,
331    /// Between mouse down and up, the position where the mouse was pressed.
332    /// Otherwise, this contains Point::MIN.
333    mouse_down_position: Point,
334    /// Node ID of the node that was clicked on.
335    /// Used for tracking drag targets.
336    left_mouse_down_target: u64,
337    /// Timestamp of the last mouse up event.
338    /// Used for tracking double/triple clicks.
339    mouse_up_timestamp: std::time::Instant,
340    /// The current mouse state.
341    mouse_state: InputMouseState,
342    /// Whether the mouse is currently being dragged.
343    mouse_is_drag: bool,
344    /// The number of clicks that have happened in a row.
345    /// Gets reset when the mouse was released for a while.
346    mouse_click_counter: CoordType,
347    /// The path to the node that was clicked on.
348    mouse_down_node_path: Vec<u64>,
349    /// The position of the first click in a double/triple click series.
350    first_click_position: Point,
351    /// The node ID of the node that was first clicked on
352    /// in a double/triple click series.
353    first_click_target: u64,
354
355    /// Path to the currently focused node.
356    focused_node_path: Vec<u64>,
357    /// Contains the last element in [`Tui::focused_node_path`].
358    /// This way we can track if the focus changed, because then we
359    /// need to scroll the node into view if it's within a scrollarea.
360    focused_node_for_scrolling: u64,
361
362    /// A list of cached text buffers used for [`Context::editline()`].
363    cached_text_buffers: Vec<CachedTextBuffer>,
364
365    /// The clipboard contents.
366    clipboard: Clipboard,
367
368    settling_have: i32,
369    settling_want: i32,
370    read_timeout: time::Duration,
371}
372
373impl Tui {
374    /// Creates a new [`Tui`] instance for storing state across frames.
375    pub fn new() -> apperr::Result<Self> {
376        let arena_prev = Arena::new(128 * MEBI)?;
377        let arena_next = Arena::new(128 * MEBI)?;
378        // SAFETY: Since `prev_tree` refers to `arena_prev`/`arena_next`, from its POV the lifetime
379        // is `'static`, requiring us to use `transmute` to circumvent the borrow checker.
380        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    /// Sets up the framebuffer's color palette.
428    pub fn setup_indexed_colors(&mut self, colors: [u32; INDEXED_COLORS_COUNT]) {
429        self.framebuffer.set_indexed_colors(colors);
430    }
431
432    /// Set up translations for Ctrl/Alt/Shift modifiers.
433    pub fn setup_modifier_translations(&mut self, translations: ModifierTranslations) {
434        self.modifier_translations = translations;
435    }
436
437    /// Set the default background color for floaters (dropdowns, etc.).
438    pub fn set_floater_default_bg(&mut self, color: u32) {
439        self.floater_default_bg = color;
440    }
441
442    /// Set the default foreground color for floaters (dropdowns, etc.).
443    pub fn set_floater_default_fg(&mut self, color: u32) {
444        self.floater_default_fg = color;
445    }
446
447    /// Set the default background color for modals.
448    pub fn set_modal_default_bg(&mut self, color: u32) {
449        self.modal_default_bg = color;
450    }
451
452    /// Set the default foreground color for modals.
453    pub fn set_modal_default_fg(&mut self, color: u32) {
454        self.modal_default_fg = color;
455    }
456
457    /// If the TUI is currently running animations, etc.,
458    /// this will return a timeout smaller than [`time::Duration::MAX`].
459    pub fn read_timeout(&mut self) -> time::Duration {
460        mem::replace(&mut self.read_timeout, time::Duration::MAX)
461    }
462
463    /// Returns the viewport size.
464    pub fn size(&self) -> Size {
465        // We don't use the size stored in the framebuffer, because until
466        // `render()` is called, the framebuffer will use a stale size.
467        self.size
468    }
469
470    /// Returns an indexed color from the framebuffer.
471    #[inline]
472    pub fn indexed(&self, index: IndexedColor) -> u32 {
473        self.framebuffer.indexed(index)
474    }
475
476    /// Returns an indexed color from the framebuffer with the given alpha.
477    /// See [`Framebuffer::indexed_alpha()`].
478    #[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    /// Returns a color in contrast with the given color.
484    /// See [`Framebuffer::contrasted()`].
485    pub fn contrasted(&self, color: u32) -> u32 {
486        self.framebuffer.contrasted(color)
487    }
488
489    /// Returns the clipboard.
490    pub fn clipboard_ref(&self) -> &Clipboard {
491        &self.clipboard
492    }
493
494    /// Returns the clipboard (mutable).
495    pub fn clipboard_mut(&mut self) -> &mut Clipboard {
496        &mut self.clipboard
497    }
498
499    /// Starts a new frame and returns a [`Context`] for it.
500    pub fn create_context<'a, 'input>(
501        &'a mut self,
502        input: Option<Input<'input>>,
503    ) -> Context<'a, 'input> {
504        // SAFETY: Since we have a unique `&mut self`, nothing is holding onto `arena_prev`,
505        // which will become `arena_next` and get reset. It's safe to reset and reuse its memory.
506        mem::swap(&mut self.arena_prev, &mut self.arena_next);
507        unsafe { self.arena_next.reset(0) };
508
509        // In the input handler below we transformed a mouse up into a release event.
510        // Now, a frame later, we must reset it back to none, to stop it from triggering things.
511        // Same for Scroll events.
512        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        // `input_consumed` should be `true` if we're in the settling phase which is indicated by
527        // `self.needs_settling() == true`. However, there's a possibility for it being true from
528        // a previous frame, and we do have fresh new input. In that case want `input_consumed`
529        // to be false of course which is ensured by checking for `input.is_none()`.
530        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                // TODO: the .len()==1 check causes us to ignore keyboard inputs that are faster than we process them.
546                // For instance, imagine the user presses "A" twice and we happen to read it in a single chunk.
547                // This causes us to ignore the keyboard input here. We need a way to inform the caller over
548                // how much of the input text we actually processed in a single frame. Or perhaps we could use
549                // the needs_settling logic?
550                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; // Needed for `mouse_down`
577                let mut focused_node = None; // Needed for `mouse_down` and `is_click`
578                if mouse_down || mouse_up {
579                    // Roots (aka windows) are ordered in Z order, so we iterate
580                    // them in reverse order, from topmost to bottommost.
581                    for root in self.prev_tree.iterate_roots_rev() {
582                        // Find the node that contains the cursor.
583                        Tree::visit_all(root, root, true, |node| {
584                            let n = node.borrow();
585                            if !n.outer_clipped.contains(next_position) {
586                                // Skip the entire sub-tree, because it doesn't contain the cursor.
587                                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                        // This root/window contains the cursor.
597                        // We don't care about any lower roots.
598                        if hovered_node.is_some() {
599                            break;
600                        }
601
602                        // This root is modal and swallows all clicks,
603                        // no matter whether the click was inside it or not.
604                        if matches!(root.borrow().content, NodeContent::Modal(_)) {
605                            break;
606                        }
607                    }
608                }
609
610                if mouse_down {
611                    // Transition from no mouse input to some mouse input --> Record the mouse down position.
612                    Self::build_node_path(hovered_node, &mut self.mouse_down_node_path);
613
614                    // On left-mouse-down we change focus.
615                    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(); // See `needs_more_settling()`.
620                    }
621
622                    // Double-/Triple-/Etc.-clicks are triggered on mouse-down,
623                    // unlike the first initial click, which is triggered on mouse-up.
624                    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                            // If the cursor moved / the focus changed in between, or if the user did a slow click,
631                            // we reset the click counter. On mouse-up it'll transition to a regular click.
632                            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                    // Gets reset at the start of this function.
642                    self.left_mouse_down_target = target;
643                    self.mouse_down_position = next_position;
644                } else if mouse_up {
645                    // Transition from some mouse input to no mouse input --> The mouse button was released.
646                    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                        // If `left_mouse_down_target == 0`, then it wasn't a left-click, in which case
652                        // the target gets reset. Same, if the focus changed in between any clicks.
653                        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                        // No focus change, and no previous clicks? This is an initial, regular click.
658                        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            // Every time there's input, we naturally need to re-render at least once.
678            self.settling_have = 0;
679            self.settling_want = 1;
680        }
681
682        // TODO: There should be a way to do this without unsafe.
683        // Allocating from the arena borrows the arena, and so allocating the tree here borrows self.
684        // This conflicts with us passing a mutable reference to `self` into the struct below.
685        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        // If this hits, you forgot to block_end() somewhere. The best way to figure
710        // out where is to do a binary search of commenting out code in main.rs.
711        debug_assert!(
712            ctx.tree.current_node.borrow().stack_parent.is_none(),
713            "Dangling parent! Did you miss a block_end?"
714        );
715
716        // End the root node.
717        ctx.block_end();
718
719        // Ensure that focus doesn't escape the active modal.
720        if let Some(node) = ctx.last_modal
721            && !self.is_subtree_focused(&node.borrow())
722        {
723            ctx.steal_focus_for(node);
724        }
725
726        // If nodes have appeared or disappeared, we need to re-render.
727        // Same, if the focus has changed (= changes the highlight color, etc.).
728        let mut needs_settling = ctx.needs_settling;
729        needs_settling |= self.prev_tree.checksum != ctx.tree.checksum;
730
731        // Adopt the new tree and recalculate the node hashmap.
732        //
733        // SAFETY: The memory used by the tree is owned by the `self.arena_next` right now.
734        // Stealing the tree here thus doesn't need to copy any memory unless someone resets the arena.
735        // (The arena is reset in `reset()` above.)
736        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 the user pressed Escape, we move the focus to a parent node.
743        if !ctx.input_consumed && ctx.consume_shortcut(vk::ESCAPE) {
744            focus_path_pop_min = 1;
745        }
746
747        // Remove any unknown nodes from the focus path.
748        // It's important that we do this after the tree has been swapped out,
749        // so that pop_focusable_node() has access to the newest version of the tree.
750        needs_settling |= self.pop_focusable_node(focus_path_pop_min);
751
752        // `needs_more_settling()` depends on the current value
753        // of `settling_have` and so we increment it first.
754        self.settling_have += 1;
755
756        if needs_settling {
757            self.needs_more_settling();
758        }
759
760        // Remove cached text editors that are no longer in use.
761        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    /// After you finished processing all input, continue redrawing your UI until this returns false.
829    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 the focus has changed, the new node may need to be re-rendered.
835        // Same, every time we encounter a previously unknown node via `get_prev_node`,
836        // because that means it likely failed to get crucial information such as the layout size.
837        if cfg!(debug_assertions) && self.settling_have == 15 {
838            breakpoint();
839        }
840        self.settling_want = (self.settling_have + 1).min(20);
841    }
842
843    /// Renders the last frame into the framebuffer and returns the VT output.
844    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    /// Recursively renders each node and its children.
854    #[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            // ┌────┐
865            {
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            // │    │
879            {
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            // └────┘
896            {
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                    // Account for the scrollbar.
978                    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                    // Render the scrollbar.
989                    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        // The section of `text` that is skipped by the ellipsis.
1037        let mut skipped = 0..0;
1038        // The number of columns skipped by the ellipsis.
1039        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                    // Chunk is fully inside the text skipped by the ellipsis.
1095                    // We don't need to render it at all.
1096                    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    /// Outputs a debug string of the layout and focus tree.
1121    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        // We construct the focused_node_path always with at least 1 element (the root id).
1225        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        // Remove `pop_minimum`-many nodes from the end of the focus path.
1240        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            // Truncate the path so that it only contains nodes that still exist.
1246            let Some(node) = self.prev_node_map.get(id) else {
1247                break;
1248            };
1249
1250            let n = node.borrow();
1251            // If the caller requested upward movement, pop out of the current focus void, if any.
1252            // This is kind of janky, to be fair.
1253            if pop_minimum != 0 && n.attributes.focus_void {
1254                break;
1255            }
1256
1257            // Skip over those that aren't focusable.
1258            if n.attributes.focusable {
1259                // At this point `n.depth == i` should be true,
1260                // but I kind of don't want to rely on that.
1261                len = i + 1;
1262            }
1263        }
1264
1265        self.focused_node_path.truncate(len);
1266
1267        // If it's empty now, push `ROOT_ID` because there must always be >=1 element.
1268        if self.focused_node_path.is_empty() {
1269            self.focused_node_path.push(ROOT_ID);
1270        }
1271
1272        // Return true if the focus path changed.
1273        let last_after = self.focused_node_path.last().cloned().unwrap_or(0);
1274        last_before != last_after
1275    }
1276
1277    // Scroll the focused node(s) into view inside scrollviews
1278    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            // Node not found because we're using the old layout tree.
1286            // Retry in the next rendering loop.
1287            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
1311/// Context is a temporary object that is created for each frame.
1312/// Its primary purpose is to build a UI tree.
1313pub struct Context<'a, 'input> {
1314    tui: &'a mut Tui,
1315
1316    /// Current text input, if any.
1317    input_text: Option<&'input str>,
1318    /// Current keyboard input, if any.
1319    input_keyboard: Option<InputKey>,
1320    input_mouse_modifiers: InputKeyMod,
1321    input_mouse_click: CoordType,
1322    /// By how much the mouse wheel was scrolled since the last frame.
1323    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    /// Get an arena for temporary allocations such as for [`arena_format`].
1345    pub fn arena(&self) -> &'a Arena {
1346        // TODO:
1347        // `Context` borrows `Tui` for lifetime 'a, so `self.tui` should be `&'a Tui`, right?
1348        // And if I do `&self.tui.arena` then that should be 'a too, right?
1349        // Searching for and failing to find a workaround for this was _very_ annoying.
1350        //
1351        // SAFETY: Both the returned reference and its allocations outlive &self.
1352        unsafe { mem::transmute::<&'_ Arena, &'a Arena>(&self.tui.arena_next) }
1353    }
1354
1355    /// Returns the viewport size.
1356    pub fn size(&self) -> Size {
1357        self.tui.size()
1358    }
1359
1360    /// Returns an indexed color from the framebuffer.
1361    #[inline]
1362    pub fn indexed(&self, index: IndexedColor) -> u32 {
1363        self.tui.framebuffer.indexed(index)
1364    }
1365
1366    /// Returns an indexed color from the framebuffer with the given alpha.
1367    /// See [`Framebuffer::indexed_alpha()`].
1368    #[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    /// Returns a color in contrast with the given color.
1374    /// See [`Framebuffer::contrasted()`].
1375    pub fn contrasted(&self, color: u32) -> u32 {
1376        self.tui.framebuffer.contrasted(color)
1377    }
1378
1379    /// Returns the clipboard.
1380    pub fn clipboard_ref(&self) -> &Clipboard {
1381        &self.tui.clipboard
1382    }
1383
1384    /// Returns the clipboard (mutable).
1385    pub fn clipboard_mut(&mut self) -> &mut Clipboard {
1386        &mut self.tui.clipboard
1387    }
1388
1389    /// Tell the UI framework that your state changed and you need another layout pass.
1390    pub fn needs_rerender(&mut self) {
1391        // If this hits, the call stack is responsible is trying to deadlock you.
1392        debug_assert!(self.tui.settling_have < 15);
1393        self.needs_settling = true;
1394    }
1395
1396    /// Begins a generic UI block (container) with a unique ID derived from the given `classname`.
1397    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        // If this hits, you have tried to create a block with the same ID as a previous one
1407        // somewhere up this call stack. Change the classname, or use next_block_id_mixin().
1408        // TODO: HashMap
1409        #[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    /// Ends the current UI block, returning to its parent container.
1425    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        // At this point, it's more like "focus_well?" instead of "focus_well!".
1432        let focus_well = self.tree.last_node;
1433
1434        // Remember the focused node, if any, because once the code below runs,
1435        // we need it for the `Tree::visit_all` call.
1436        if self.is_focused() {
1437            self.focused_node = Some(focus_well);
1438        }
1439
1440        // The mere fact that there's a `focused_node` indicates that we're the
1441        // first `block_end()` call that's a focus well and also contains the focus.
1442        let Some(focused) = self.focused_node else {
1443            return;
1444        };
1445
1446        // Filter down to nodes that are focus wells and contain the focus. They're
1447        // basically the "tab container". We test for the node depth to ensure that
1448        // we don't accidentally pick a focus well next to or inside the focused node.
1449        {
1450            let n = focus_well.borrow();
1451            if !n.attributes.focus_well || n.depth > focused.borrow().depth {
1452                return;
1453            }
1454        }
1455
1456        // Filter down to Tab/Shift+Tab inputs.
1457        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        // We may be in a focus void right now (= doesn't want to be tabbed into),
1472        // so first we must go up the tree until we're outside of it.
1473        loop {
1474            if ptr::eq(focused_start, focus_well) {
1475                // If we hit the root / focus well, we weren't in a focus void,
1476                // and can reset `focused_before` to the current focused node.
1477                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    /// Mixes in an extra value to the next UI block's ID for uniqueness.
1509    /// Use this when you build a list of items with the same classname.
1510    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    /// If this is the first time the current node is being drawn,
1520    /// it'll steal the active focus.
1521    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    /// Steals the focus unconditionally.
1533    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    /// If the current node owns the focus, it'll be given to the parent.
1545    pub fn toss_focus_up(&mut self) {
1546        if self.tui.pop_focusable_node(1) {
1547            self.needs_rerender();
1548        }
1549    }
1550
1551    /// If the parent node owns the focus, it'll be given to the current node.
1552    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        // Mark the parent as focusable, so that if the user presses Escape,
1561        // and `block_end` bubbles the focus up the tree, it'll stop on our parent,
1562        // which will then focus us on the next iteration.
1563        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    /// Causes keyboard focus to be unable to escape this node and its children.
1573    /// It's a "well" because if the focus is inside it, it can't escape.
1574    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    /// Explicitly sets the intrinsic size of the current node.
1580    /// The intrinsic size is the size the node ideally wants to be.
1581    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    /// Turns the current node into a floating node,
1588    /// like a popup, modal or a tooltip.
1589    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                // By not giving such floats a parent, they get the same origin as the original root node,
1597                // but they also gain their own "root id" in the tree. That way, their focus path is totally unique,
1598                // which means that we can easily check if a modal is open by calling `is_focused()` on the original root.
1599                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    /// Gives the current node a border.
1618    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    /// Sets the current node's position inside the parent.
1624    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    /// Assigns padding to the current node.
1630    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    /// Assigns a sRGB background color to the current node.
1645    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    /// Assigns a sRGB foreground color to the current node.
1651    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    /// Applies reverse-video to the current node:
1657    /// Background and foreground colors are swapped.
1658    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    /// Checks if the current keyboard input matches the given shortcut,
1664    /// consumes it if it is and returns true in that case.
1665    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    /// Returns current keyboard input, if any.
1675    /// Returns None if the input was already consumed.
1676    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    /// Returns whether the mouse was pressed down on the current node.
1692    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    /// Returns whether the mouse was pressed down on the current node's subtree.
1698    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    /// Returns whether the current node is focused.
1704    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    /// Returns whether the current node's subtree is focused.
1710    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    /// Begins a modal window. Call [`Context::modal_end()`].
1716    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    /// Ends the current modal window block.
1742    /// Returns true if the user pressed Escape (a request to close).
1743    pub fn modal_end(&mut self) -> bool {
1744        self.block_end();
1745
1746        // Consume the input unconditionally, so that the root (the "main window")
1747        // doesn't accidentally receive any input via `consume_shortcut()`.
1748        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    /// Begins a table block. Call [`Context::table_end()`].
1758    /// Tables are the primary way to create a grid layout,
1759    /// and to layout controls on a single row (= a table with 1 row).
1760    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    /// Assigns widths to the columns of the current table.
1771    /// By default, the table will left-align all columns.
1772    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    /// Assigns the gap between cells in the current table.
1783    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    /// Starts the next row in the current table.
1793    pub fn table_next_row(&mut self) {
1794        {
1795            let current_node = self.tree.current_node.borrow();
1796
1797            // If this is the first call to table_next_row() inside a new table, the
1798            // current_node will refer to the table. Otherwise, it'll refer to the current row.
1799            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                // Neither the current nor its parent nodes are a table?
1806                // You definitely called this outside of a table block.
1807                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    /// Ends the current table block.
1824    pub fn table_end(&mut self) {
1825        let current_node = self.tree.current_node.borrow();
1826
1827        // If this is the first call to table_next_row() inside a new table, the
1828        // current_node will refer to the table. Otherwise, it'll refer to the current row.
1829        if !matches!(current_node.content, NodeContent::Table(_)) {
1830            self.block_end();
1831            self.table_end_row();
1832        }
1833
1834        self.block_end(); // table
1835        self.table_move_focus(vk::UP, vk::DOWN);
1836    }
1837
1838    fn table_move_focus(&mut self, prev_key: InputKey, next_key: InputKey) {
1839        // Filter down to table rows that are focused.
1840        if !self.contains_focus() {
1841            return;
1842        }
1843
1844        // Filter down to our prev/next inputs.
1845        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        // Iterate through the cells in the row / the rows in the table, looking for focused_id.
1864        // Take note of the previous and next focusable cells / rows around the focused one.
1865        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    /// Creates a simple text label.
1898    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    /// Creates a styled text label.
1905    ///
1906    /// # Example
1907    /// ```
1908    /// use edit::framebuffer::IndexedColor;
1909    /// use edit::tui::Context;
1910    ///
1911    /// fn draw(ctx: &mut Context) {
1912    ///     ctx.styled_label_begin("label");
1913    ///     // Shows "Hello" in the inherited foreground color.
1914    ///     ctx.styled_label_add_text("Hello");
1915    ///     // Shows ", World!" next to "Hello" in red.
1916    ///     ctx.styled_label_set_foreground(ctx.indexed(IndexedColor::Red));
1917    ///     ctx.styled_label_add_text(", World!");
1918    /// }
1919    /// ```
1920    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    /// Changes the active pencil color of the current label.
1930    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    /// Changes the active pencil attributes of the current label.
1947    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    /// Adds text to the current label.
1960    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    /// Ends the current label block.
1970    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    /// Sets the overflow behavior of the current label.
1988    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    /// Creates a button with the given text.
1998    /// Returns true if the button was activated.
1999    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    /// Creates a checkbox with the given text.
2009    /// Returns true if the checkbox was activated.
2010    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    /// Creates a text input field.
2043    /// Returns true if the text contents changed.
2044    pub fn editline(&mut self, classname: &'static str, text: &mut dyn WriteableDocument) -> bool {
2045        self.textarea_internal(classname, TextBufferPayload::Editline(text))
2046    }
2047
2048    /// Creates a text area.
2049    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                    // If the node is not in the cache, we need to create a new one.
2077                    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            // SAFETY: *Assuming* that there are no duplicate node IDs in the tree that
2090            // would cause this cache slot to be overwritten, then this operation is safe.
2091            // The text buffer cache will keep the buffer alive for us long enough.
2092            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                    // Subtract -1 to account for the scrollbar.
2127                    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            // Scrolling works even if the node isn't focused.
2193            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 the editor is only 1 line tall we can't possibly scroll up or down.
2225                        if height >= 2 {
2226                            fn calc(min: CoordType, max: CoordType, mouse: CoordType) -> CoordType {
2227                                // Otherwise, the scroll zone is up to 3 lines at the top/bottom.
2228                                let zone_height = ((max - min) / 2).min(3);
2229
2230                                // The .y positions where the scroll zones begin:
2231                                // Mouse coordinates above top and below bottom respectively.
2232                                let scroll_min = min + zone_height;
2233                                let scroll_max = max - zone_height - 1;
2234
2235                                // Calculate the delta for scrolling up or down.
2236                                let delta_min = (mouse - scroll_min).clamp(-zone_height, 0);
2237                                let delta_max = (mouse - scroll_max).clamp(0, zone_height);
2238
2239                                // If I didn't mess up my logic here, only one of the two values can possibly be !=0.
2240                                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                                        // TODO: Untested because Windows Terminal surprisingly doesn't support Shift+Click.
2267                                        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                        // The textarea supports 1 height worth of "scrolling beyond the end".
2287                        // `track_height` is the same as the viewport height.
2288                        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                        // If this is just a simple input field, don't consume Tab (= early return).
2332                        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                        // If this is just a simple input field, don't consume Enter (= early return).
2343                        return false;
2344                    }
2345                    write = b"\n";
2346                }
2347                vk::ESCAPE => {
2348                    // If there was a selection, clear it and show the cursor (= fallthrough).
2349                    if !tb.clear_selection() {
2350                        if single_line {
2351                            // If this is just a simple input field, don't consume the escape key
2352                            // (early return) and don't show the cursor (= return false).
2353                            return false;
2354                        }
2355
2356                        // If this is a textarea, don't show the cursor if
2357                        // the escape key was pressed and nothing happened.
2358                        make_cursor_visible = false;
2359                    }
2360                }
2361                vk::PRIOR => {
2362                    let height = node_prev.inner.height() - 1;
2363
2364                    // If the cursor was already on the first line,
2365                    // move it to the start of the buffer.
2366                    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 the cursor was already on the last line,
2386                    // move it to the end of the buffer.
2387                    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 word-wrap is enabled and the user presses End the first time,
2425                        // it moves to the start of the visual line. The second time they
2426                        // press it, it moves to the start of the logical line.
2427                        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 word-wrap is enabled and the user presses Home the first time,
2460                        // it moves to the start of the visual line. The second time they
2461                        // press it, it moves to the start of the logical line.
2462                        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 the line has some indentation and the user pressed Home,
2478                        // the first time it'll stop at the indentation. The second time
2479                        // they press it, it'll move to the true start of the line.
2480                        //
2481                        // If the cursor is already at the start of the line,
2482                        // we move it back to the end of the indentation.
2483                        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 there's a selection we put the cursor above it.
2519                            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 the cursor was already on the first line,
2526                            // move it to the start of the buffer.
2527                            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 the cursor was already on the first line,
2540                            // move it to the start of the buffer.
2541                            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                            // TODO: Add cursor above
2552                        }
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 there's a selection we put the cursor below it.
2580                            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 the cursor was already on the last line,
2587                            // move it to the end of the buffer.
2588                            if y >= tb.visual_line_count() {
2589                                x = CoordType::MAX;
2590                            }
2591
2592                            tb.cursor_move_to_visual(Point { x, y });
2593
2594                            // If we fell into the `if y >= tb.get_visual_line_count()` above, we wanted to
2595                            // update the `preferred_column` but didn't know yet what it was. Now we know!
2596                            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 the cursor was already on the last line,
2606                            // move it to the end of the buffer.
2607                            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                            // TODO: Add cursor above
2622                        }
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                        // On macOS, terminals commonly emit the Emacs style
2643                        // Alt+B (ESC b) sequence for Alt+Left.
2644                        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                        // On macOS, terminals commonly emit the Emacs style
2651                        // Alt+F (ESC f) sequence for Alt+Right.
2652                        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 up if the cursor is above the visible area.
2721        scroll_y = scroll_y.min(cursor_y);
2722        // Scroll down if the cursor is below the visible area.
2723        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    /// Creates a scrollable area.
2747    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        // Ensure that attribute modifications apply to the outer container.
2770        self.tree.last_node = container_node;
2771    }
2772
2773    /// Scrolls the current scrollable area to the given position.
2774    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    /// Ends the current scrollarea block.
2784    pub fn scrollarea_end(&mut self) {
2785        self.block_end(); // content block
2786        self.block_end(); // outer container
2787
2788        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                            // We don't need to look up the previous track node,
2814                            // since it has a fixed size based on the container size.
2815                            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    /// Creates a list where exactly one item is selected.
2874    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    /// Creates a list item with the given text.
2896    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    /// Creates a list item consisting of a styled label.
2903    /// See [`Context::styled_label_begin`].
2904    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    /// Ends the current styled list item.
2915    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            // Inherit the default selection & Click changes selection
2936            selected_now = selected_before || (select && content.selected == 0) || focused;
2937
2938            // Note down the selected node for keyboard navigation.
2939            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        // Clicking an item activates it
2949        let clicked =
2950            !self.input_consumed && (self.input_mouse_click == 2 && self.was_mouse_down());
2951        // Pressing Enter on a selected item activates it as well
2952        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    /// [`Context::steal_focus`], but for a list view.
2971    ///
2972    /// This exists, because didn't want to figure out how to get
2973    /// [`Context::styled_list_item_end`] to recognize a regular,
2974    /// programmatic focus steal.
2975    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    /// Ends the current list block.
2988    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        // Now that we know which item is selected we can mark it as such.
3071        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        // Now that we know which item is selected we can mark it as such.
3078        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 the list has focus, we also delegate focus to the selected item and colorize it.
3085        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    /// Creates a menubar, to be shown at the top of the screen.
3096    pub fn menubar_begin(&mut self) {
3097        self.table_begin("menubar");
3098        self.attr_focus_well();
3099        self.table_next_row();
3100    }
3101
3102    /// Appends a menu to the current menubar.
3103    ///
3104    /// Returns true if the menu is open. Continue appending items to it in that case.
3105    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    /// Appends a button to the current menu.
3155    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    /// Appends a checkbox to the current menu.
3165    /// Returns true if the checkbox was activated.
3166    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        // First menu item? Steal focus.
3177        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            // TODO: This should reassign the previous focused path.
3198            self.needs_rerender();
3199            Tui::clean_node_path(&mut self.tui.focused_node_path);
3200        }
3201
3202        clicked
3203    }
3204
3205    /// Ends the current menu.
3206    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                // If the focus is on the menubar, and the user presses up/down,
3215                // focus the first/last item of the flyout respectively.
3216                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                // Otherwise, if the menu is the focused one and the
3227                // user presses Escape, pass focus back to the menubar.
3228                self.tui.pop_focusable_node(1);
3229            }
3230        }
3231    }
3232
3233    /// Ends the current menubar.
3234    pub fn menubar_end(&mut self) {
3235        self.table_end();
3236    }
3237
3238    /// Renders a button label with an optional accelerator character
3239    /// May also renders a checkbox or square brackets for inline buttons
3240    fn button_label(&mut self, classname: &'static str, text: &str, style: ButtonStyle) {
3241        // Label prefix
3242        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        // Label text
3250        match style.accelerator {
3251            Some(accelerator) if accelerator.is_ascii_uppercase() => {
3252                // Complex case:
3253                // Locate the offset of the accelerator character in the label text
3254                let mut off = text.len();
3255                for (i, c) in text.bytes().enumerate() {
3256                    // Perfect match (uppercase character) --> stop
3257                    if c as char == accelerator {
3258                        off = i;
3259                        break;
3260                    }
3261                    // Inexact match (lowercase character) --> use first hit
3262                    if (c & !0x20) as char == accelerator && off == text.len() {
3263                        off = i;
3264                    }
3265                }
3266
3267                if off < text.len() {
3268                    // Add an underline to the accelerator.
3269                    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                    // Add the accelerator in parentheses and underline it.
3276                    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                // Simple case:
3287                // no accelerator character
3288                self.styled_label_add_text(text);
3289            }
3290        }
3291        // Label postfix
3292        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/// See [`Tree::visit_all`].
3326#[derive(Clone, Copy)]
3327enum VisitControl {
3328    Continue,
3329    SkipChildren,
3330    Stop,
3331}
3332
3333/// Stores the root of the "DOM" tree of the UI.
3334struct 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    /// Creates a new tree inside the given arena.
3347    /// A single root node is added for the main contents.
3348    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    /// Appends a child node to the current node.
3373    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        // wymix is weak, but both checksum and node.id are proper random, so... it's not *that* bad.
3405        self.checksum = wymix(self.checksum, n.id);
3406    }
3407
3408    /// Removes the current node from its parent and appends it as a new root.
3409    /// Used for [`Context::attr_float`].
3410    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    /// Completes the current node and moves focus to the parent.
3446    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    /// Visits all nodes under and including `root` in depth order.
3483    /// Starts with node `start`.
3484    ///
3485    /// WARNING: Breaks in hilarious ways if `start` is not within `root`.
3486    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                        // Depth first search: It has a child? Go there.
3502                        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                    // If we hit the root while going up, we restart the traversal at
3513                    // `root` going down again until we hit `start` again.
3514                    let n = node.borrow();
3515                    if n.depth <= root_depth {
3516                        break 'traverse;
3517                    }
3518
3519                    // Go to the parent's next sibling. --> Next subtree.
3520                    if let Some(sibling) = n.siblings.get(siblings_idx) {
3521                        node = sibling;
3522                        break;
3523                    }
3524
3525                    // Out of children? Go back to the parent.
3526                    node = n.parent.unwrap();
3527                }
3528            }
3529
3530            // We're done once we wrapped around to the `start`.
3531            !ptr::eq(node, start)
3532        } {}
3533    }
3534}
3535
3536/// A hashmap of node IDs to nodes.
3537///
3538/// This map uses a simple open addressing scheme with linear probing.
3539/// It's fast, simple, and sufficient for the small number of nodes we have.
3540struct 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    /// Creates a new node map for the given tree.
3554    fn new(arena: &'a Arena, tree: &Tree<'a>) -> Self {
3555        // Since we aren't expected to have millions of nodes,
3556        // we allocate 4x the number of slots for a 25% fill factor.
3557        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    /// Gets a node by its ID.
3587    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    // Specifies the origin of the container relative to the container size. [0, 1]
3604    gravity_x: f32,
3605    gravity_y: f32,
3606    // Specifies an offset from the origin in cells.
3607    offset_x: f32,
3608    offset_y: f32,
3609}
3610
3611/// NOTE: Must not contain items that require drop().
3612#[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, // Prevents focus from leaving via Tab
3623    focus_void: bool, // Prevents focus from entering via Tab
3624}
3625
3626/// NOTE: Must not contain items that require drop().
3627struct ListContent<'a> {
3628    selected: u64,
3629    // Points to the Node that holds this ListContent instance, if any>.
3630    selected_node: Option<&'a NodeCell<'a>>,
3631}
3632
3633/// NOTE: Must not contain items that require drop().
3634struct TableContent<'a> {
3635    columns: Vec<CoordType, &'a Arena>,
3636    cell_gap: Size,
3637}
3638
3639/// NOTE: Must not contain items that require drop().
3640struct 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
3649/// NOTE: Must not contain items that require drop().
3650struct TextContent<'a> {
3651    text: ArenaString<'a>,
3652    chunks: Vec<StyledTextChunk, &'a Arena>,
3653    overflow: Overflow,
3654}
3655
3656/// NOTE: Must not contain items that require drop().
3657struct TextareaContent<'a> {
3658    buffer: &'a TextBufferCell,
3659
3660    // Carries over between frames.
3661    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/// NOTE: Must not contain items that require drop().
3672#[derive(Clone)]
3673struct ScrollareaContent {
3674    scroll_offset: Point,
3675    scroll_offset_y_drag_start: CoordType,
3676    thumb_height: CoordType,
3677}
3678
3679/// NOTE: Must not contain items that require drop().
3680#[derive(Default)]
3681enum NodeContent<'a> {
3682    #[default]
3683    None,
3684    List(ListContent<'a>),
3685    Modal(ArenaString<'a>), // title
3686    Table(TableContent<'a>),
3687    Text(TextContent<'a>),
3688    Textarea(TextareaContent<'a>),
3689    Scrollarea(ScrollareaContent),
3690}
3691
3692/// NOTE: Must not contain items that require drop().
3693#[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/// NOTE: Must not contain items that require drop().
3713#[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/// A node in the UI tree.
3735///
3736/// NOTE: Must not contain items that require drop().
3737#[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,         // in screen-space, calculated during layout
3757    inner: Rect,         // in screen-space, calculated during layout
3758    outer_clipped: Rect, // in screen-space, calculated during layout, restricted to the viewport
3759    inner_clipped: Rect, // in screen-space, calculated during layout, restricted to the viewport
3760}
3761
3762impl Node<'_> {
3763    /// Given an outer rectangle (including padding and borders) of this node,
3764    /// this returns the inner rectangle (excluding padding and borders).
3765    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    /// Given an intrinsic size (excluding padding and borders) of this node,
3779    /// this returns the outer size (including padding and borders).
3780    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    /// Computes the intrinsic size of this node and its children.
3799    fn compute_intrinsic_size(&mut self) {
3800        match &mut self.content {
3801            NodeContent::Table(spec) => {
3802                // Calculate each row's height and the maximum width of each of its columns.
3803                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 the spec.columns[] value is positive, it's an absolute width.
3814                        // Otherwise, it's a fraction of the remaining space.
3815                        //
3816                        // TODO: The latter is computed incorrectly.
3817                        // Example: If the items are "a","b","c" then the intrinsic widths are [1,1,1].
3818                        // If the column spec is [0,-3,-1], then this code assigns an intrinsic row
3819                        // width of 3, but it should be 5 (1+1+3), because the spec says that the
3820                        // last column (flexible 1/1) must be 3 times as wide as the 2nd one (1/3rd).
3821                        // It's not a big deal yet, because such functionality isn't needed just yet.
3822                        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                // Assuming each column has the width of the widest cell in that column,
3834                // calculate the total width of the table.
3835                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                // Assign the total width to each row.
3842                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                // Assign the total width/height to the table.
3857                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    /// Lays out the children of this node.
3886    /// The clip rect restricts "rendering" to a certain area (the viewport).
3887    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                // content available viewport size (-1 for the track)
3940                let sx = self.inner.right - self.inner.left;
3941                let sy = self.inner.bottom - self.inner.top;
3942                // actual content size
3943                let cx = sx;
3944                let cy = content.intrinsic_size.height.max(sy);
3945                // scroll offset
3946                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}