mirror of
https://github.com/wez/wezterm.git
synced 2024-12-25 14:22:37 +03:00
gui: add UIItem concept
The idea here is to make it a bit easier to do hit detection for UI elements; today we've been duplicating position math between the render and the mouse movement handlers, with both pieces of code knowing the location of the UI element. UIItem allows the render phase to record the position, which allows the mouse phase to be a more independent lookup against the registered elements. This makes it easier to add more UI elements in the future.
This commit is contained in:
parent
0bbb8cc37c
commit
ab8c8dc6c9
@ -106,6 +106,22 @@ pub enum TermWindowNotif {
|
||||
Apply(Box<dyn FnOnce(&mut TermWindow) + Send + Sync>),
|
||||
}
|
||||
|
||||
#[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<UIItem>,
|
||||
last_mouse_coords: (usize, i64),
|
||||
last_mouse_terminal_coords: (usize, StableRowIndex),
|
||||
scroll_drag_start: Option<isize>,
|
||||
@ -231,6 +248,8 @@ pub struct TermWindow {
|
||||
|
||||
palette: Option<ColorPalette>,
|
||||
|
||||
ui_items: Vec<UIItem>,
|
||||
|
||||
event_states: HashMap<String, EventState>,
|
||||
has_animation: RefCell<Option<Instant>>,
|
||||
/// 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));
|
||||
|
@ -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<UIItem> {
|
||||
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<dyn Pane>,
|
||||
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<dyn Pane>,
|
||||
@ -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 {
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user