From 741bee461cbdaf0236eaa55dc86dc84b01613128 Mon Sep 17 00:00:00 2001 From: FluxTape Date: Mon, 26 Feb 2024 18:47:46 +0100 Subject: Implement warp-mouse-to-focus --- src/input.rs | 94 ++++++++++++++++++++++++++++++++++++++++--------- src/layout/monitor.rs | 8 +++++ src/layout/workspace.rs | 38 ++++++++++++++++++++ src/niri.rs | 80 +++++++++++++++++++++++++++++++++++++++-- src/utils/mod.rs | 4 +++ 5 files changed, 206 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/input.rs b/src/input.rs index 7f3e6fa7..9536731a 100644 --- a/src/input.rs +++ b/src/input.rs @@ -411,139 +411,166 @@ impl State { } Action::MoveColumnLeft => { self.niri.layout.move_left(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveColumnRight => { self.niri.layout.move_right(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveColumnToFirst => { self.niri.layout.move_column_to_first(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveColumnToLast => { self.niri.layout.move_column_to_last(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveWindowDown => { self.niri.layout.move_down(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveWindowUp => { self.niri.layout.move_up(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveWindowDownOrToWorkspaceDown => { self.niri.layout.move_down_or_to_workspace_down(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveWindowUpOrToWorkspaceUp => { self.niri.layout.move_up_or_to_workspace_up(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::ConsumeOrExpelWindowLeft => { self.niri.layout.consume_or_expel_window_left(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::ConsumeOrExpelWindowRight => { self.niri.layout.consume_or_expel_window_right(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusColumnLeft => { self.niri.layout.focus_left(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusColumnRight => { self.niri.layout.focus_right(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusColumnFirst => { self.niri.layout.focus_column_first(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusColumnLast => { self.niri.layout.focus_column_last(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusWindowDown => { self.niri.layout.focus_down(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusWindowUp => { self.niri.layout.focus_up(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusWindowOrWorkspaceDown => { self.niri.layout.focus_window_or_workspace_down(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusWindowOrWorkspaceUp => { self.niri.layout.focus_window_or_workspace_up(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveWindowToWorkspaceDown => { self.niri.layout.move_to_workspace_down(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveWindowToWorkspaceUp => { self.niri.layout.move_to_workspace_up(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveWindowToWorkspace(idx) => { let idx = idx.saturating_sub(1) as usize; self.niri.layout.move_to_workspace(idx); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveColumnToWorkspaceDown => { self.niri.layout.move_column_to_workspace_down(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveColumnToWorkspaceUp => { self.niri.layout.move_column_to_workspace_up(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::MoveColumnToWorkspace(idx) => { let idx = idx.saturating_sub(1) as usize; self.niri.layout.move_column_to_workspace(idx); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusWorkspaceDown => { self.niri.layout.switch_workspace_down(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusWorkspaceUp => { self.niri.layout.switch_workspace_up(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } Action::FocusWorkspace(idx) => { let idx = idx.saturating_sub(1) as usize; self.niri.layout.switch_workspace(idx); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } @@ -559,11 +586,14 @@ impl State { } Action::ConsumeWindowIntoColumn => { self.niri.layout.consume_into_column(); + // This does not cause immediate focus or window size change, so warping mouse to + // focus won't do anything here. // FIXME: granular self.niri.queue_redraw_all(); } Action::ExpelWindowFromColumn => { self.niri.layout.expel_from_column(); + self.maybe_warp_cursor_to_focus(); // FIXME: granular self.niri.queue_redraw_all(); } @@ -581,81 +611,105 @@ impl State { Action::FocusMonitorLeft => { if let Some(output) = self.niri.output_left() { self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::FocusMonitorRight => { if let Some(output) = self.niri.output_right() { self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::FocusMonitorDown => { if let Some(output) = self.niri.output_down() { self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::FocusMonitorUp => { if let Some(output) = self.niri.output_up() { self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveWindowToMonitorLeft => { if let Some(output) = self.niri.output_left() { self.niri.layout.move_to_output(&output); self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveWindowToMonitorRight => { if let Some(output) = self.niri.output_right() { self.niri.layout.move_to_output(&output); self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveWindowToMonitorDown => { if let Some(output) = self.niri.output_down() { self.niri.layout.move_to_output(&output); self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveWindowToMonitorUp => { if let Some(output) = self.niri.output_up() { self.niri.layout.move_to_output(&output); self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveColumnToMonitorLeft => { if let Some(output) = self.niri.output_left() { self.niri.layout.move_column_to_output(&output); self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveColumnToMonitorRight => { if let Some(output) = self.niri.output_right() { self.niri.layout.move_column_to_output(&output); self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveColumnToMonitorDown => { if let Some(output) = self.niri.output_down() { self.niri.layout.move_column_to_output(&output); self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveColumnToMonitorUp => { if let Some(output) = self.niri.output_up() { self.niri.layout.move_column_to_output(&output); self.niri.layout.focus_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::SetColumnWidth(change) => { @@ -672,25 +726,33 @@ impl State { Action::MoveWorkspaceToMonitorLeft => { if let Some(output) = self.niri.output_left() { self.niri.layout.move_workspace_to_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveWorkspaceToMonitorRight => { if let Some(output) = self.niri.output_right() { self.niri.layout.move_workspace_to_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveWorkspaceToMonitorDown => { if let Some(output) = self.niri.output_down() { self.niri.layout.move_workspace_to_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } Action::MoveWorkspaceToMonitorUp => { if let Some(output) = self.niri.output_up() { self.niri.layout.move_workspace_to_output(&output); - self.move_cursor_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } } } } diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index f817ef1a..e9fb2271 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -583,6 +583,14 @@ impl Monitor { self.clean_up_workspaces(); } + /// Returns the geometry of the active tile relative to and clamped to the output. + /// + /// During animations, assumes the final view position. + pub fn active_tile_visual_rectangle(&self) -> Option> { + // FIXME: switch gesture. + self.active_workspace_ref().active_tile_visual_rectangle() + } + pub fn window_under( &self, pos_within_output: Point, diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 41c9e140..2c18b40e 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -181,6 +181,15 @@ impl OutputId { } } +impl ViewOffsetAdjustment { + pub fn target_view_offset(&self) -> f64 { + match self { + ViewOffsetAdjustment::Animation(anim) => anim.to(), + ViewOffsetAdjustment::Gesture(gesture) => gesture.current_view_offset, + } + } +} + impl ColumnWidth { fn resolve(self, options: &Options, view_width: i32) -> i32 { match self { @@ -1124,6 +1133,31 @@ impl Workspace { first.chain(rest) } + fn active_column_ref(&self) -> Option<&Column> { + if self.columns.is_empty() { + return None; + } + Some(&self.columns[self.active_column_idx]) + } + + /// Returns the geometry of the active tile relative to and clamped to the view. + /// + /// During animations, assumes the final view position. + pub fn active_tile_visual_rectangle(&self) -> Option> { + let col = self.active_column_ref()?; + let view_pos = self + .view_offset_adj + .as_ref() + .map_or(self.view_offset, |adj| adj.target_view_offset() as i32); + + let tile_pos = Point::from((-view_pos, col.tile_y(col.active_tile_idx))); + let tile_size = col.active_tile_ref().tile_size(); + let tile_rect = Rectangle::from_loc_and_size(tile_pos, tile_size); + + let view = Rectangle::from_loc_and_size((0, 0), self.view_size); + view.intersection(tile_rect) + } + pub fn window_under( &self, pos: Point, @@ -2010,6 +2044,10 @@ impl Column { pos }) } + + fn active_tile_ref(&self) -> &Tile { + &self.tiles[self.active_tile_idx] + } } fn compute_new_view_offset( diff --git a/src/niri.rs b/src/niri.rs index 37c1e40c..fe14dca3 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -112,7 +112,7 @@ use crate::ui::hotkey_overlay::HotkeyOverlay; use crate::ui::screenshot_ui::{ScreenshotUi, ScreenshotUiRenderElement}; use crate::utils::spawning::CHILD_ENV; use crate::utils::{ - center, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8, + center, center_f64, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8, }; use crate::window::Unmapped; use crate::{animation, niri_render_elements}; @@ -197,7 +197,6 @@ pub struct Niri { // popup grabs are active (which means the real keyboard focus is on a popup descending from // this toplevel surface). pub keyboard_focus: Option, - pub idle_inhibiting_surfaces: HashSet, pub is_fdo_idle_inhibited: Arc, @@ -314,6 +313,11 @@ struct SurfaceFrameThrottlingState { last_sent_at: RefCell>, } +pub enum CenterCoords { + Seperately, + Both, +} + #[derive(Default)] pub struct WindowOffscreenId(pub RefCell>); @@ -401,6 +405,78 @@ impl State { self.niri.queue_redraw_all(); } + /// Moves cursor within the specified rectangle, only adjusting coordinates if needed. + fn move_cursor_to_rect(&mut self, rect: Rectangle, mode: CenterCoords) -> bool { + let pointer = &self.niri.seat.get_pointer().unwrap(); + let cur_loc = pointer.current_location(); + let x_in_bound = cur_loc.x >= rect.loc.x && cur_loc.x <= rect.loc.x + rect.size.w; + let y_in_bound = cur_loc.y >= rect.loc.y && cur_loc.y <= rect.loc.y + rect.size.h; + + let p = match mode { + CenterCoords::Seperately => { + if x_in_bound && y_in_bound { + return false; + } else if y_in_bound { + // adjust x + Point::from((rect.loc.x + rect.size.w / 2.0, cur_loc.y)) + } else if x_in_bound { + // adjust y + Point::from((cur_loc.x, rect.loc.y + rect.size.h / 2.0)) + } else { + // adjust x and y + center_f64(rect) + } + } + CenterCoords::Both => { + if x_in_bound && y_in_bound { + return false; + } else { + // adjust x and y + center_f64(rect) + } + } + }; + + self.move_cursor(p); + true + } + + pub fn move_cursor_to_focused_tile(&mut self, mode: CenterCoords) -> bool { + let Some(output) = self.niri.layout.active_output() else { + return false; + }; + let output = output.clone(); + let monitor = self.niri.layout.monitor_for_output(&output).unwrap(); + + let mut rv = false; + let rect = monitor.active_tile_visual_rectangle(); + + if let Some(rect) = rect { + let output_geo = self.niri.global_space.output_geometry(&output).unwrap(); + let mut rect = rect; + rect.loc += output_geo.loc; + rv = self.move_cursor_to_rect(rect.to_f64(), mode); + } + + rv + } + + pub fn maybe_warp_cursor_to_focus(&mut self) -> bool { + if !self.niri.config.borrow().input.warp_mouse_to_focus { + return false; + } + + self.move_cursor_to_focused_tile(CenterCoords::Seperately) + } + + pub fn maybe_warp_cursor_to_focus_centered(&mut self) -> bool { + if !self.niri.config.borrow().input.warp_mouse_to_focus { + return false; + } + + self.move_cursor_to_focused_tile(CenterCoords::Both) + } + pub fn refresh_pointer_focus(&mut self) { let _span = tracy_client::span!("Niri::refresh_pointer_focus"); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index df21ffdb..e9e56bcf 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -40,6 +40,10 @@ pub fn center(rect: Rectangle) -> Point { rect.loc + rect.size.downscale(2).to_point() } +pub fn center_f64(rect: Rectangle) -> Point { + rect.loc + rect.size.downscale(2.0).to_point() +} + pub fn output_size(output: &Output) -> Size { let output_scale = output.current_scale().integer_scale(); let output_transform = output.current_transform(); -- cgit