Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/edit/src/bin/edit/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ impl Drop for RestoreModes {
// Same as in the beginning but in the reverse order.
// It also includes DECSCUSR 0 to reset the cursor style and DECTCEM to show the cursor.
// We specifically don't reset mode 1036, because most applications expect it to be set nowadays.
sys::write_stdout("\x1b[0 q\x1b[?25h\x1b]0;\x07\x1b[?1002;1006;2004l\x1b[?1049l");
sys::write_stdout("\x1b[0 q\x1b[?25h\x1b]0;\x07\x1b[?1003;1006;2004l\x1b[?1049l");
}
}

Expand All @@ -563,11 +563,11 @@ fn setup_terminal(tui: &mut Tui, state: &mut State, vt_parser: &mut vt::Parser)
// 1049: Alternative Screen Buffer
// I put the ASB switch in the beginning, just in case the terminal performs
// some additional state tracking beyond the modes we enable/disable.
// 1002: Cell Motion Mouse Tracking
// 1003: Any Event Mouse Tracking
// 1006: SGR Mouse Mode
// 2004: Bracketed Paste Mode
// 1036: Xterm: "meta sends escape" (Alt keypresses should be encoded with ESC + char)
"\x1b[?1049h\x1b[?1002;1006;2004h\x1b[?1036h",
"\x1b[?1049h\x1b[?1003;1006;2004h\x1b[?1036h",
// OSC 4 color table requests for indices 0 through 15 (base colors).
"\x1b]4;0;?;1;?;2;?;3;?;4;?;5;?;6;?;7;?\x07",
"\x1b]4;8;?;9;?;10;?;11;?;12;?;13;?;14;?;15;?\x07",
Expand Down
103 changes: 70 additions & 33 deletions crates/edit/src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ pub struct Tui {
mouse_click_counter: CoordType,
/// The path to the node that was clicked on.
mouse_down_node_path: Vec<u64>,
/// Path to the node currently under the mouse cursor.
hovered_node_path: Vec<u64>,
/// This is check mouse hover enabled
mouse_hover_enabled: bool,
/// The position of the first click in a double/triple click series.
first_click_position: Point,
/// The node ID of the node that was first clicked on
Expand Down Expand Up @@ -407,6 +411,8 @@ impl Tui {
mouse_is_drag: false,
mouse_click_counter: 0,
mouse_down_node_path: Vec::with_capacity(16),
hovered_node_path: Vec::with_capacity(16),
mouse_hover_enabled: true,
first_click_position: Point::MIN,
first_click_target: 0,

Expand All @@ -422,10 +428,16 @@ impl Tui {
read_timeout: time::Duration::MAX,
};
Self::clean_node_path(&mut tui.mouse_down_node_path);
Self::clean_node_path(&mut tui.hovered_node_path);
Self::clean_node_path(&mut tui.focused_node_path);
Ok(tui)
}

// Helper to check if a node is in the hover path
fn is_subtree_hovered(&self, node: &Node) -> bool {
self.hovered_node_path.get(node.depth) == Some(&node.id)
}

/// Sets up the framebuffer's color palette.
pub fn setup_indexed_colors(&mut self, colors: [StraightRgba; INDEXED_COLORS_COUNT]) {
self.framebuffer.set_indexed_colors(colors);
Expand Down Expand Up @@ -548,6 +560,7 @@ impl Tui {
self.size = resize;
}
Some(Input::Text(text)) => {
self.mouse_hover_enabled = false;
input_text = Some(text);
// TODO: the .len()==1 check causes us to ignore keyboard inputs that are faster than we process them.
// For instance, imagine the user presses "A" twice and we happen to read it in a single chunk.
Expand All @@ -560,15 +573,18 @@ impl Tui {
}
}
Some(Input::Paste(paste)) => {
self.mouse_hover_enabled = false;
let clipboard = self.clipboard_mut();
clipboard.write(paste);
clipboard.mark_as_synchronized();
input_keyboard = Some(kbmod::CTRL | vk::V);
}
Some(Input::Keyboard(keyboard)) => {
self.mouse_hover_enabled = false;
input_keyboard = Some(keyboard);
}
Some(Input::Mouse(mouse)) => {
self.mouse_hover_enabled = true;
let mut next_state = mouse.state;
let next_position = mouse.position;
let next_scroll = mouse.scroll;
Expand All @@ -583,36 +599,35 @@ impl Tui {

let mut hovered_node = None; // Needed for `mouse_down`
let mut focused_node = None; // Needed for `mouse_down` and `is_click`
if mouse_down || mouse_up {
// Roots (aka windows) are ordered in Z order, so we iterate
// them in reverse order, from topmost to bottommost.
for root in self.prev_tree.iterate_roots_rev() {
// Find the node that contains the cursor.
Tree::visit_all(root, root, true, |node| {
let n = node.borrow();
if !n.outer_clipped.contains(next_position) {
// Skip the entire sub-tree, because it doesn't contain the cursor.
return VisitControl::SkipChildren;
}
hovered_node = Some(node);
if n.attributes.focusable {
focused_node = Some(node);
}
VisitControl::Continue
});

// This root/window contains the cursor.
// We don't care about any lower roots.
if hovered_node.is_some() {
break;
// Calculate the hovered node for ALL mouse events, not just clicks.
// Roots (aka windows) are ordered in Z order...
for root in self.prev_tree.iterate_roots_rev() {
Tree::visit_all(root, root, true, |node| {
let n = node.borrow();
if !n.outer_clipped.contains(next_position) {
return VisitControl::SkipChildren;
}

// This root is modal and swallows all clicks,
// no matter whether the click was inside it or not.
if matches!(root.borrow().content, NodeContent::Modal(_)) {
break;
hovered_node = Some(node);
if n.attributes.focusable {
focused_node = Some(node);
}
VisitControl::Continue
});
if hovered_node.is_some() {
break;
}
if matches!(root.borrow().content, NodeContent::Modal(_)) {
break;
}
}

// Update the hovered path and trigger redraw if it changed
let mut next_hovered_path = Vec::with_capacity(16);
Self::build_node_path(hovered_node, &mut next_hovered_path);
if self.hovered_node_path != next_hovered_path {
self.hovered_node_path = next_hovered_path;
self.needs_more_settling();
}

if is_scroll {
Expand Down Expand Up @@ -3165,15 +3180,25 @@ impl<'a> Context<'a, '_> {
&& !contains_focus
&& self.consume_shortcut(kbmod::ALT | InputKey::new(accelerator as u32));

if contains_focus || keyboard_focus {
// If the menubar is already active (another menu is open) and we are hovered,
// steal the focus to open this menu automatically.
if !contains_focus && self.is_hovered() {
let menubar_active = self.tui.is_subtree_focused(&self.tree.current_node.borrow());
if menubar_active {
self.steal_focus();
}
}

let is_highlighted = self.is_focused() || self.is_hovered();
if is_highlighted {
self.attr_background_rgba(self.indexed(IndexedColor::Green));
self.attr_foreground_rgba(self.contrasted(self.indexed(IndexedColor::Green)));
} else if contains_focus || keyboard_focus {
self.attr_background_rgba(self.tui.floater_default_bg);
self.attr_foreground_rgba(self.tui.floater_default_fg);
}

if self.is_focused() {
self.attr_background_rgba(self.indexed(IndexedColor::Green));
self.attr_foreground_rgba(self.contrasted(self.indexed(IndexedColor::Green)));
}

if contains_focus || keyboard_focus {
self.next_block_id_mixin(mixin);
self.table_begin("flyout");
self.attr_float(FloatSpec {
Expand All @@ -3186,6 +3211,9 @@ impl<'a> Context<'a, '_> {
self.attr_border();
self.attr_focus_well();

self.attr_background_rgba(self.tui.floater_default_bg);
self.attr_foreground_rgba(self.tui.floater_default_fg);

if keyboard_focus {
self.steal_focus();
}
Expand Down Expand Up @@ -3223,7 +3251,7 @@ impl<'a> Context<'a, '_> {
self.inherit_focus();
}

if self.is_focused() {
if self.is_focused() || self.is_hovered() {
self.attr_background_rgba(self.indexed(IndexedColor::Green));
self.attr_foreground_rgba(self.contrasted(self.indexed(IndexedColor::Green)));
}
Expand Down Expand Up @@ -3280,6 +3308,15 @@ impl<'a> Context<'a, '_> {
self.table_end();
}

/// Returns whether the current node is hovered.
pub fn is_hovered(&mut self) -> bool {
if !self.tui.mouse_hover_enabled {
return false;
}
let last_node = self.tree.last_node.borrow();
self.tui.is_subtree_hovered(&last_node)
}

/// Renders a button label with an optional accelerator character
/// May also renders a checkbox or square brackets for inline buttons
fn button_label(&mut self, classname: &'static str, text: &str, style: ButtonStyle) {
Expand Down