diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index 9ece612a6..d901b7a4e 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -106,6 +106,22 @@ pub enum TermWindowNotif { Apply(Box), } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum UIItemType { + TabBar, + ScrollBar, + Split(PositionedSplit), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct UIItem { + pub x: usize, + pub y: usize, + pub width: usize, + pub height: usize, + pub item_type: UIItemType, +} + #[derive(Clone, Default)] pub struct SemanticZoneCache { seqno: SequenceNo, @@ -198,6 +214,7 @@ pub struct TermWindow { show_scroll_bar: bool, tab_bar: TabBarState, pub right_status: String, + last_ui_item: Option, last_mouse_coords: (usize, i64), last_mouse_terminal_coords: (usize, StableRowIndex), scroll_drag_start: Option, @@ -231,6 +248,8 @@ pub struct TermWindow { palette: Option, + ui_items: Vec, + event_states: HashMap, has_animation: RefCell>, /// We use this to attempt to do something reasonable @@ -586,6 +605,8 @@ impl TermWindow { scheduled_animation: RefCell::new(None), allow_images: true, semantic_zones: HashMap::new(), + ui_items: vec![], + last_ui_item: None, }; let tw = Rc::new(RefCell::new(myself)); diff --git a/wezterm-gui/src/termwindow/mouseevent.rs b/wezterm-gui/src/termwindow/mouseevent.rs index c82324daf..80107d197 100644 --- a/wezterm-gui/src/termwindow/mouseevent.rs +++ b/wezterm-gui/src/termwindow/mouseevent.rs @@ -1,6 +1,6 @@ use crate::tabbar::TabBarItem; use crate::termwindow::keyevent::window_mods_to_termwiz_mods; -use crate::termwindow::{ScrollHit, TMB}; +use crate::termwindow::{PositionedSplit, ScrollHit, UIItem, UIItemType, TMB}; use ::window::{ MouseButtons as WMB, MouseCursor, MouseEvent, MouseEventKind as WMEK, MousePress, WindowOps, }; @@ -17,6 +17,37 @@ use wezterm_term::input::MouseEventKind as TMEK; use wezterm_term::{LastMouseClick, StableRowIndex}; impl super::TermWindow { + fn resolve_ui_item(&self, event: &MouseEvent) -> Option { + let x = event.coords.x; + let y = event.coords.y; + self.ui_items + .iter() + .rev() + .find(|item| { + x >= item.x as isize + && x <= (item.x + item.width) as isize + && y >= item.y as isize + && y <= (item.y + item.height) as isize + }) + .cloned() + } + + fn leave_ui_item(&mut self, item: &UIItem) { + match item.item_type { + UIItemType::TabBar => { + self.update_title_post_status(); + } + UIItemType::ScrollBar | UIItemType::Split(_) => {} + } + } + + fn enter_ui_item(&mut self, item: &UIItem) { + match item.item_type { + UIItemType::TabBar => {} + UIItemType::ScrollBar | UIItemType::Split(_) => {} + } + } + pub fn mouse_event_impl(&mut self, event: MouseEvent, context: &dyn WindowOps) { let pane = match self.get_active_pane_or_overlay() { Some(pane) => pane, @@ -48,7 +79,6 @@ impl super::TermWindow { } else { 0 } as i64; - let was_in_tab_bar = self.show_tab_bar && self.last_mouse_coords.1 == 0; let in_tab_bar = self.show_tab_bar && y == tab_bar_y && event.coords.y >= 0; let x = (event @@ -68,14 +98,9 @@ impl super::TermWindow { self.last_mouse_coords = (x, y); - let in_scroll_bar = self.show_scroll_bar && x >= self.terminal_size.cols as usize; // y position relative to top of viewport (not including tab bar) let term_y = y.saturating_sub(first_line_offset); - if was_in_tab_bar && !in_tab_bar { - self.update_title_post_status(); - } - match event.kind { WMEK::Release(ref press) => { self.current_mouse_buttons.retain(|p| p != press); @@ -197,15 +222,52 @@ impl super::TermWindow { _ => {} } - if in_tab_bar { - self.mouse_event_tab_bar(x, event, context); - } else if in_scroll_bar { - self.mouse_event_scroll_bar(pane, event, context); + let ui_item = self.resolve_ui_item(&event); + + match (self.last_ui_item.take(), &ui_item) { + (Some(prior), Some(item)) => { + self.leave_ui_item(&prior); + self.enter_ui_item(item); + } + (Some(prior), None) => { + self.leave_ui_item(&prior); + } + (None, Some(item)) => { + self.enter_ui_item(item); + } + (None, None) => {} + } + + if let Some(item) = ui_item { + self.mouse_event_ui_item(item, pane, x, term_y, event, context); } else { self.mouse_event_terminal(pane, x, term_y, event, context); } } + fn mouse_event_ui_item( + &mut self, + item: UIItem, + pane: Rc, + x: usize, + _y: i64, + event: MouseEvent, + context: &dyn WindowOps, + ) { + self.last_ui_item.replace(item.clone()); + match item.item_type { + UIItemType::TabBar => { + self.mouse_event_tab_bar(x, event, context); + } + UIItemType::ScrollBar => { + self.mouse_event_scroll_bar(pane, event, context); + } + UIItemType::Split(split) => { + self.mouse_event_split(split, event, context); + } + } + } + pub fn mouse_event_tab_bar(&mut self, x: usize, event: MouseEvent, context: &dyn WindowOps) { match event.kind { WMEK::Press(MousePress::Left) => match self.tab_bar.hit_test(x) { @@ -295,6 +357,22 @@ impl super::TermWindow { context.set_cursor(Some(MouseCursor::Arrow)); } + pub fn mouse_event_split( + &mut self, + split: PositionedSplit, + event: MouseEvent, + context: &dyn WindowOps, + ) { + context.set_cursor(Some(match &split.direction { + SplitDirection::Horizontal => MouseCursor::SizeLeftRight, + SplitDirection::Vertical => MouseCursor::SizeUpDown, + })); + + if event.kind == WMEK::Press(MousePress::Left) { + self.split_drag_start.replace(split); + } + } + pub fn mouse_event_terminal( &mut self, mut pane: Rc, @@ -303,42 +381,7 @@ impl super::TermWindow { event: MouseEvent, context: &dyn WindowOps, ) { - let mut on_split = None; let mut is_click_to_focus = false; - if y >= 0 { - let y = y as usize; - - for split in self.get_splits() { - on_split = match split.direction { - SplitDirection::Horizontal => { - if x == split.left && y >= split.top && y <= split.top + split.size { - Some(SplitDirection::Horizontal) - } else { - None - } - } - SplitDirection::Vertical => { - if y == split.top && x >= split.left && x <= split.left + split.size { - Some(SplitDirection::Vertical) - } else { - None - } - } - }; - - if on_split.is_some() { - if event.kind == WMEK::Press(MousePress::Left) { - context.set_cursor(on_split.map(|d| match d { - SplitDirection::Horizontal => MouseCursor::SizeLeftRight, - SplitDirection::Vertical => MouseCursor::SizeUpDown, - })); - self.split_drag_start.replace(split); - return; - } - break; - } - } - } for pos in self.get_panes_to_render() { if y >= pos.top as i64 @@ -434,20 +477,14 @@ impl super::TermWindow { } }; - context.set_cursor(Some(match on_split { - Some(SplitDirection::Horizontal) => MouseCursor::SizeLeftRight, - Some(SplitDirection::Vertical) => MouseCursor::SizeUpDown, - None => { - if self.current_highlight.is_some() { - // When hovering over a hyperlink, show an appropriate - // mouse cursor to give the cue that it is clickable - MouseCursor::Hand - } else if pane.is_mouse_grabbed() { - MouseCursor::Arrow - } else { - MouseCursor::Text - } - } + context.set_cursor(Some(if self.current_highlight.is_some() { + // When hovering over a hyperlink, show an appropriate + // mouse cursor to give the cue that it is clickable + MouseCursor::Hand + } else if pane.is_mouse_grabbed() { + MouseCursor::Arrow + } else { + MouseCursor::Text })); let event_trigger_type = match &event.kind { diff --git a/wezterm-gui/src/termwindow/render.rs b/wezterm-gui/src/termwindow/render.rs index 65964a864..d1aaac72c 100644 --- a/wezterm-gui/src/termwindow/render.rs +++ b/wezterm-gui/src/termwindow/render.rs @@ -4,6 +4,7 @@ use crate::glyphcache::{CachedGlyph, GlyphCache}; use crate::shapecache::*; use crate::termwindow::{ BorrowedShapeCacheKey, MappedQuads, RenderState, ScrollHit, ShapedInfo, TermWindowNotif, + UIItem, UIItemType, }; use ::window::bitmaps::atlas::OutOfTextureSpace; use ::window::bitmaps::{TextureCoord, TextureRect, TextureSize}; @@ -228,7 +229,6 @@ impl super::TermWindow { let config = &self.config; let palette = pos.pane.palette(); - let background_color = palette.resolve_bg(wezterm_term::color::ColorAttribute::Default); let first_line_offset = if self.show_tab_bar && !self.config.tab_bar_at_bottom { 1 } else { @@ -370,11 +370,11 @@ impl super::TermWindow { cols: self.terminal_size.cols as _, ..dims }; - let tab_bar_y = if self.config.tab_bar_at_bottom { - let avail_height = self.dimensions.pixel_height.saturating_sub( - (self.config.window_padding.top + self.config.window_padding.bottom) as usize, - ); + let avail_height = self.dimensions.pixel_height.saturating_sub( + (self.config.window_padding.top + self.config.window_padding.bottom) as usize, + ); + let tab_bar_y = if self.config.tab_bar_at_bottom { let num_rows = avail_height as usize / self.render_metrics.cell_size.height as usize; @@ -382,6 +382,20 @@ impl super::TermWindow { } else { 0 }; + + // Register the tab bar location + self.ui_items.push(UIItem { + x: 0, + width: self.dimensions.pixel_width, + y: if self.config.tab_bar_at_bottom { + avail_height - self.render_metrics.cell_size.height as usize + } else { + 0 + }, + height: self.render_metrics.cell_size.height as usize, + item_type: UIItemType::TabBar, + }); + self.render_screen_line_opengl( RenderScreenLineOpenGLParams { line_idx: tab_bar_y, @@ -414,22 +428,16 @@ impl super::TermWindow { // do a per-pane scrollbar. That will require more extensive // changes to ScrollHit, mouse positioning, PositionedPane // and tab size calculation. - if pos.is_active { - let (thumb_top, thumb_size, color) = if self.show_scroll_bar { - let info = ScrollHit::thumb( - &*pos.pane, - current_viewport, - self.terminal_size, - &self.dimensions, - ); - let thumb_top = info.top as f32; - let thumb_size = info.height as f32; - let color = rgbcolor_to_window_color(palette.scrollbar_thumb); - (thumb_top, thumb_size, color) - } else { - let color = rgbcolor_to_window_color(background_color); - (0., 0., color) - }; + if pos.is_active && self.show_scroll_bar { + let info = ScrollHit::thumb( + &*pos.pane, + current_viewport, + self.terminal_size, + &self.dimensions, + ); + let thumb_top = info.top as f32; + let thumb_size = info.height as f32; + let color = rgbcolor_to_window_color(palette.scrollbar_thumb); let mut quad = quads.allocate()?; @@ -443,6 +451,15 @@ impl super::TermWindow { let right = self.dimensions.pixel_width as f32 / 2.; let left = right - padding; + // Register the scroll bar location + self.ui_items.push(UIItem { + x: self.dimensions.pixel_width - padding as usize, + width: padding as usize, + y: 0, + height: self.dimensions.pixel_height, + item_type: UIItemType::ScrollBar, + }); + quad.set_fg_color(color); quad.set_position(left, top, right, bottom); quad.set_texture(white_space); @@ -673,6 +690,14 @@ impl super::TermWindow { pos_x + cell_width, pos_y + split.size as f32 * cell_height, ); + self.ui_items.push(UIItem { + x: self.config.window_padding.left as usize + (split.left * cell_width as usize), + width: cell_width as usize, + y: self.config.window_padding.top as usize + + (split.top + first_row_offset) * cell_height as usize, + height: split.size * cell_height as usize, + item_type: UIItemType::Split(split.clone()), + }); } else { quad.set_position( pos_x, @@ -680,6 +705,14 @@ impl super::TermWindow { pos_x + split.size as f32 * cell_width, pos_y + cell_height, ); + self.ui_items.push(UIItem { + x: self.config.window_padding.left as usize + (split.left * cell_width as usize), + width: split.size * cell_width as usize, + y: self.config.window_padding.top as usize + + (split.top + first_row_offset) * cell_height as usize, + height: cell_height as usize, + item_type: UIItemType::Split(split.clone()), + }); } Ok(()) @@ -691,6 +724,9 @@ impl super::TermWindow { gl_state.glyph_vertex_buffer.clear_quad_allocation(); } + // Clear out UI item positions; we'll rebuild these as we render + self.ui_items.clear(); + let panes = self.get_panes_to_render(); let num_panes = panes.len();