aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server/src/tab
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-12 15:50:04 +0200
committeruser <user@clank>2026-08-12 15:50:04 +0200
commit266147d1d5ecadafda6a4893a60019c80f3c2c85 (patch)
tree761be672fca76155d407ffda8be4a0c99033fc62 /zellij-server/src/tab
parentsl0p: document the pane status bar protocol (diff)
downloadsl0ppij-266147d1d5ecadafda6a4893a60019c80f3c2c85.tar.gz
sl0ppij-266147d1d5ecadafda6a4893a60019c80f3c2c85.tar.xz
sl0ppij-266147d1d5ecadafda6a4893a60019c80f3c2c85.zip
sl0p: highlight the status bar button under the pointer
A click target with no hover feedback gives you no way to tell what you are about to hit, which matters more here than elsewhere because the buttons sit in a frame row that is only one cell tall. The hovered button is inverted rather than merely recoloured: swapping fore/background reads regardless of which colours a theme uses, and makes "the pointer is on THIS one" unmistakable next to its unhovered neighbours. Tracked per client, because hover belongs to a pointer rather than to a pane -- with two users attached, one pointer must not light up the other's button. Stored on the pane keyed by client id, which the frame renderer already has. Updated before the action dispatch rather than in the hover action, because motion over the ACTIVE pane is dispatched as SendToTerminal, not UpdateHover -- and the pane you are about to click a button in is usually the one you are already in. Every other pane's hover for that client is cleared on each motion, so moving between panes cannot leave a button lit behind.
Diffstat (limited to 'zellij-server/src/tab')
-rw-r--r--zellij-server/src/tab/mod.rs11
-rw-r--r--zellij-server/src/tab/mouse_handler.rs43
2 files changed, 53 insertions, 1 deletions
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index 8f049cad..8da96d41 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -899,6 +899,17 @@ pub trait Pane {
fn status_bar_button_at(&self, _position: &Position) -> Option<String> {
None
}
+ fn hovered_status_bar_button(&self, _client_id: ClientId) -> Option<String> {
+ None
+ }
+ /// Returns true if the hovered button changed, i.e. a re-render is needed.
+ fn set_hovered_status_bar_button(
+ &mut self,
+ _client_id: ClientId,
+ _button_id: Option<String>,
+ ) -> bool {
+ false
+ }
fn stack_list_entry_label(&self) -> String {
self.current_title()
}
diff --git a/zellij-server/src/tab/mouse_handler.rs b/zellij-server/src/tab/mouse_handler.rs
index ab8b2383..cb1b79f6 100644
--- a/zellij-server/src/tab/mouse_handler.rs
+++ b/zellij-server/src/tab/mouse_handler.rs
@@ -410,9 +410,50 @@ impl MouseHandler {
if let Some(effect) = Self::intercept_status_bar_button_click(tab, event)? {
return Ok(effect);
}
+ // Not an intercept: hover state is updated and the event still goes on to
+ // its normal handling.
+ let hover_changed = Self::update_status_bar_hover(tab, event, client_id)?;
let context = Self::gather_mouse_event_context(tab, event, client_id)?;
let action = Self::determine_mouse_action(event, &context)?;
- Self::execute_mouse_action(tab, action, event, client_id)
+ let mut effect = Self::execute_mouse_action(tab, action, event, client_id)?;
+ if hover_changed {
+ effect.state_changed = true;
+ }
+ Ok(effect)
+ }
+
+ /// Track which status bar button the pointer is over, so it can be drawn
+ /// highlighted.
+ ///
+ /// Done here rather than in the hover action, because motion over the ACTIVE
+ /// pane is dispatched as SendToTerminal, not UpdateHover -- and the pane you
+ /// are about to click a button in is usually the one you are already in.
+ ///
+ /// Returns whether anything changed, so the caller can force a render.
+ fn update_status_bar_hover(
+ tab: &mut Tab,
+ event: &MouseEvent,
+ client_id: ClientId,
+ ) -> Result<bool> {
+ if event.event_type != MouseEventType::Motion {
+ return Ok(false);
+ }
+ let hovered = Self::get_pane_at(tab, &event.position, false)?
+ .and_then(|pane| pane.status_bar_button_at(&event.position).map(|id| (pane.pid(), id)));
+ let mut changed = false;
+ // clear every other pane's hover for this client, so moving between panes
+ // cannot leave a button lit behind us
+ let pane_ids: Vec<PaneId> = tab.get_all_pane_ids();
+ for pane_id in pane_ids {
+ let wanted = match &hovered {
+ Some((hovered_id, button_id)) if *hovered_id == pane_id => Some(button_id.clone()),
+ _ => None,
+ };
+ if let Some(pane) = tab.get_pane_with_id_mut(pane_id) {
+ changed |= pane.set_hovered_status_bar_button(client_id, wanted);
+ }
+ }
+ Ok(changed)
}
/// A click on a pane's status bar button reports it to the program in that