diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2025-04-25 09:36:50 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-04-25 02:00:18 -0700 |
| commit | af1fca35bb15b8010cd3a12bbafe71b55d9ecf57 (patch) | |
| tree | 6283896fd931b9e5244a435cee9a0c227a850c23 /src/layout/mod.rs | |
| parent | 9571d149b2cecd3df8ba3f90f0af296e9f69af6e (diff) | |
| download | niri-af1fca35bb15b8010cd3a12bbafe71b55d9ecf57.tar.gz niri-af1fca35bb15b8010cd3a12bbafe71b55d9ecf57.tar.bz2 niri-af1fca35bb15b8010cd3a12bbafe71b55d9ecf57.zip | |
Implement an Overview
Diffstat (limited to 'src/layout/mod.rs')
| -rw-r--r-- | src/layout/mod.rs | 438 |
1 files changed, 394 insertions, 44 deletions
diff --git a/src/layout/mod.rs b/src/layout/mod.rs index cdb96113..019df993 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -45,6 +45,7 @@ use niri_config::{ use niri_ipc::{ColumnDisplay, PositionChange, SizeChange}; use scrolling::{Column, ColumnWidth}; use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement; +use smithay::backend::renderer::element::utils::RescaleRenderElement; use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture}; use smithay::output::{self, Output}; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; @@ -55,7 +56,8 @@ use workspace::{WorkspaceAddWindowTarget, WorkspaceId}; pub use self::monitor::MonitorRenderElement; use self::monitor::{Monitor, WorkspaceSwitch}; use self::workspace::{OutputId, Workspace}; -use crate::animation::Clock; +use crate::animation::{Animation, Clock}; +use crate::input::swipe_tracker::SwipeTracker; use crate::layout::scrolling::ScrollDirection; use crate::niri_render_elements; use crate::render_helpers::offscreen::OffscreenData; @@ -96,6 +98,14 @@ const INTERACTIVE_MOVE_START_THRESHOLD: f64 = 256. * 256.; /// Opacity of interactively moved tiles targeting the scrolling layout. const INTERACTIVE_MOVE_ALPHA: f64 = 0.75; +/// Amount of touchpad movement to toggle the overview. +const OVERVIEW_GESTURE_MOVEMENT: f64 = 300.; + +const OVERVIEW_GESTURE_RUBBER_BAND: RubberBand = RubberBand { + stiffness: 0.5, + limit: 0.05, +}; + /// Size-relative units. pub struct SizeFrac; @@ -293,6 +303,13 @@ pub struct Layout<W: LayoutElement> { clock: Clock, /// Time that we last updated render elements for. update_render_elements_time: Duration, + /// Whether the overview is open. + /// + /// This is a boolean flag that controls things like where input goes to. The actual animation + /// is controlled by overview_progress. + overview_open: bool, + /// The overview zoom progress. + overview_progress: Option<OverviewProgress>, /// Configurable properties of the layout. options: Rc<Options>, } @@ -338,6 +355,7 @@ pub struct Options { pub preset_window_heights: Vec<PresetSize>, pub animations: niri_config::Animations, pub gestures: niri_config::Gestures, + pub overview: niri_config::Overview, // Debug flags. pub disable_resize_throttling: bool, pub disable_transactions: bool, @@ -365,6 +383,7 @@ impl Default for Options { default_column_width: None, animations: Default::default(), gestures: Default::default(), + overview: Default::default(), disable_resize_throttling: false, disable_transactions: false, preset_window_heights: vec![ @@ -493,6 +512,21 @@ pub enum HitType { }, } +#[derive(Debug)] +enum OverviewProgress { + Animation(Animation), + Gesture(OverviewGesture), +} + +#[derive(Debug)] +struct OverviewGesture { + tracker: SwipeTracker, + /// Start point. + start: f64, + /// Current progress. + value: f64, +} + impl<W: LayoutElement> InteractiveMoveState<W> { fn moving(&self) -> Option<&InteractiveMoveData<W>> { match self { @@ -510,16 +544,16 @@ impl<W: LayoutElement> InteractiveMoveState<W> { } impl<W: LayoutElement> InteractiveMoveData<W> { - fn tile_render_location(&self) -> Point<f64, Logical> { + fn tile_render_location(&self, zoom: f64) -> Point<f64, Logical> { let scale = Scale::from(self.output.current_scale().fractional_scale()); let window_size = self.tile.window_size(); let pointer_offset_within_window = Point::from(( window_size.w * self.pointer_ratio_within_window.0, window_size.h * self.pointer_ratio_within_window.1, )); - let pos = - self.pointer_pos_within_output - pointer_offset_within_window - self.tile.window_loc() - + self.tile.render_offset(); + let pos = self.pointer_pos_within_output + - (pointer_offset_within_window + self.tile.window_loc() - self.tile.render_offset()) + .upscale(zoom); // Round to physical pixels. pos.to_physical_precise_round(scale).to_logical(scale) } @@ -553,6 +587,15 @@ impl HitType { tile.hit(pos_within_tile) .map(|hit| (tile.window(), hit.offset_win_pos(tile_pos))) } + + pub fn to_activate(self) -> Self { + match self { + HitType::Input { .. } => HitType::Activate { + is_tab_indicator: false, + }, + HitType::Activate { .. } => self, + } + } } impl Options { @@ -594,6 +637,7 @@ impl Options { default_column_width, animations: config.animations.clone(), gestures: config.gestures, + overview: config.overview, disable_resize_throttling: config.debug.disable_resize_throttling, disable_transactions: config.debug.disable_transactions, preset_window_heights, @@ -611,6 +655,19 @@ impl Options { } } +impl OverviewProgress { + fn value(&self) -> f64 { + match self { + OverviewProgress::Animation(anim) => anim.value(), + OverviewProgress::Gesture(gesture) => gesture.value, + } + } + + fn is_animation(&self) -> bool { + matches!(self, OverviewProgress::Animation(_)) + } +} + impl<W: LayoutElement> Layout<W> { pub fn new(clock: Clock, config: &Config) -> Self { Self::with_options_and_workspaces(clock, config, Options::from_config(config)) @@ -625,6 +682,8 @@ impl<W: LayoutElement> Layout<W> { dnd: None, clock, update_render_elements_time: Duration::ZERO, + overview_open: false, + overview_progress: None, options: Rc::new(options), } } @@ -648,6 +707,8 @@ impl<W: LayoutElement> Layout<W> { dnd: None, clock, update_render_elements_time: Duration::ZERO, + overview_open: false, + overview_progress: None, options: opts, } } @@ -751,6 +812,8 @@ impl<W: LayoutElement> Layout<W> { let mut monitor = Monitor::new(output, workspaces, self.clock.clone(), self.options.clone()); monitor.active_workspace_idx = active_workspace_idx; + monitor.overview_open = self.overview_open; + monitor.set_overview_progress(self.overview_progress.as_ref()); monitors.push(monitor); MonitorSet::Normal { @@ -789,6 +852,8 @@ impl<W: LayoutElement> Layout<W> { let mut monitor = Monitor::new(output, workspaces, self.clock.clone(), self.options.clone()); monitor.active_workspace_idx = active_workspace_idx; + monitor.overview_open = self.overview_open; + monitor.set_overview_progress(self.overview_progress.as_ref()); MonitorSet::Normal { monitors: vec![monitor], @@ -1418,7 +1483,7 @@ impl<W: LayoutElement> Layout<W> { let mut target = Rectangle::from_size(Size::from((width, height))); // FIXME: ideally this shouldn't include the tile render offset, but the code // duplication would be a bit annoying for this edge case. - target.loc.y -= move_.tile_render_location().y; + target.loc.y -= move_.tile_render_location(1.).y; target.loc.y -= move_.tile.window_loc().y; return target; } @@ -2279,8 +2344,19 @@ impl<W: LayoutElement> Layout<W> { ) -> Option<(&W, HitType)> { if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { if move_.output == *output { - let tile_pos = move_.tile_render_location(); - HitType::hit_tile(&move_.tile, tile_pos, pos_within_output) + if self.overview_progress.is_some() { + let zoom = self.overview_zoom(); + let tile_pos = move_.tile_render_location(zoom); + let pos_within_tile = (pos_within_output - tile_pos).downscale(zoom); + // During the overview animation, we cannot do input hits because we cannot + // really represent scaled windows properly. + let (win, hit) = + HitType::hit_tile(&move_.tile, Point::from((0., 0.)), pos_within_tile)?; + Some((win, hit.to_activate())) + } else { + let tile_pos = move_.tile_render_location(1.); + HitType::hit_tile(&move_.tile, tile_pos, pos_within_output) + } } else { None } @@ -2316,6 +2392,36 @@ impl<W: LayoutElement> Layout<W> { mon.resize_edges_under(pos_within_output) } + pub fn workspace_under( + &self, + extended_bounds: bool, + output: &Output, + pos_within_output: Point<f64, Logical>, + ) -> Option<&Workspace<W>> { + if self + .interactive_moved_window_under(output, pos_within_output) + .is_some() + { + return None; + } + + let MonitorSet::Normal { monitors, .. } = &self.monitor_set else { + return None; + }; + + let mon = monitors.iter().find(|mon| &mon.output == output)?; + if extended_bounds { + mon.workspace_under(pos_within_output).map(|(ws, _)| ws) + } else { + mon.workspace_under_narrow(pos_within_output) + } + } + + pub fn overview_zoom(&self) -> f64 { + let progress = self.overview_progress.as_ref().map(|p| p.value()); + compute_overview_zoom(&self.options, progress) + } + #[cfg(test)] fn verify_invariants(&self) { use std::collections::HashSet; @@ -2324,6 +2430,8 @@ impl<W: LayoutElement> Layout<W> { use crate::layout::monitor::WorkspaceSwitch; + let zoom = self.overview_zoom(); + let mut move_win_id = None; if let Some(state) = &self.interactive_move { match state { @@ -2352,7 +2460,7 @@ impl<W: LayoutElement> Layout<W> { base options adjusted for output scale" ); - let tile_pos = move_.tile_render_location(); + let tile_pos = move_.tile_render_location(zoom); let rounded_pos = tile_pos.to_physical_precise_round(scale).to_logical(scale); // Tile position must be rounded to physical pixels. @@ -2460,6 +2568,12 @@ impl<W: LayoutElement> Layout<W> { "monitor options must be synchronized with layout" ); + assert_eq!(self.overview_open, monitor.overview_open); + assert_eq!( + self.overview_progress.as_ref().map(|p| p.value()), + monitor.overview_progress_value() + ); + if let Some(WorkspaceSwitch::Animation(anim)) = &monitor.workspace_switch { let before_idx = anim.from() as usize; let after_idx = anim.to() as usize; @@ -2650,6 +2764,8 @@ impl<W: LayoutElement> Layout<W> { // Scroll the view if needed. if let Some((output, pos_within_output)) = dnd_scroll { if let Some(mon) = self.monitor_for_output_mut(&output) { + let zoom = mon.overview_zoom(); + if let Some((ws, geo)) = mon.workspace_under(pos_within_output) { let ws_id = ws.id(); let ws = mon @@ -2657,7 +2773,18 @@ impl<W: LayoutElement> Layout<W> { .iter_mut() .find(|ws| ws.id() == ws_id) .unwrap(); - ws.dnd_scroll_gesture_scroll(pos_within_output - geo.loc); + // As far as the DnD scroll gesture is concerned, the workspace spans across + // the whole monitor horizontally. + let ws_pos = Point::from((0., geo.loc.y)); + ws.dnd_scroll_gesture_scroll(pos_within_output - ws_pos, 1. / zoom); + } + } + } + + if !self.overview_open { + if let Some(OverviewProgress::Animation(anim)) = &mut self.overview_progress { + if anim.is_done() { + self.overview_progress = None; } } } @@ -2665,6 +2792,7 @@ impl<W: LayoutElement> Layout<W> { match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => { for mon in monitors { + mon.set_overview_progress(self.overview_progress.as_ref()); mon.advance_animations(); } } @@ -2697,6 +2825,14 @@ impl<W: LayoutElement> Layout<W> { } } + if self + .overview_progress + .as_ref() + .is_some_and(|p| p.is_animation()) + { + return true; + } + let MonitorSet::Normal { monitors, .. } = &self.monitor_set else { return false; }; @@ -2719,9 +2855,10 @@ impl<W: LayoutElement> Layout<W> { self.update_render_elements_time = self.clock.now(); + let zoom = self.overview_zoom(); if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { if output.map_or(true, |output| move_.output == *output) { - let pos_within_output = move_.tile_render_location(); + let pos_within_output = move_.tile_render_location(zoom); let view_rect = Rectangle::new(pos_within_output.upscale(-1.), output_size(&move_.output)); move_.tile.update_render_elements(true, view_rect); @@ -2745,6 +2882,7 @@ impl<W: LayoutElement> Layout<W> { let is_active = self.is_active && idx == *active_monitor_idx && !matches!(self.interactive_move, Some(InteractiveMoveState::Moving(_))); + mon.set_overview_progress(self.overview_progress.as_ref()); mon.update_render_elements(is_active); } } @@ -2798,6 +2936,7 @@ impl<W: LayoutElement> Layout<W> { let _span = tracy_client::span!("Layout::update_insert_hint::update"); if let Some(mon) = self.monitor_for_output_mut(&move_.output) { + let zoom = mon.overview_zoom(); if let Some((ws, geo)) = mon.workspace_under(move_.pointer_pos_within_output) { let ws_id = ws.id(); let ws = mon @@ -2805,8 +2944,8 @@ impl<W: LayoutElement> Layout<W> { .iter_mut() .find(|ws| ws.id() == ws_id) .unwrap(); - - let pos_within_workspace = move_.pointer_pos_within_output - geo.loc; + let pos_within_workspace = + (move_.pointer_pos_within_output - geo.loc).downscale(zoom); let position = ws.scrolling_insert_position(pos_within_workspace); let rules = move_.tile.window().rules(); @@ -3645,6 +3784,9 @@ impl<W: LayoutElement> Layout<W> { timestamp: Duration, is_touchpad: bool, ) -> Option<Option<Output>> { + let zoom = self.overview_zoom(); + let delta_x = delta_x / zoom; + let monitors = match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => monitors, MonitorSet::NoOutputs { .. } => return None, @@ -3684,6 +3826,77 @@ impl<W: LayoutElement> Layout<W> { None } + pub fn overview_gesture_begin(&mut self) { + self.overview_open = true; + + let value = self.overview_progress.take().map_or(0., |p| p.value()); + let gesture = OverviewGesture { + tracker: SwipeTracker::new(), + start: value, + value, + }; + self.overview_progress = Some(OverviewProgress::Gesture(gesture)); + + self.set_monitors_overview_state(); + } + + pub fn overview_gesture_update(&mut self, delta_y: f64, timestamp: Duration) -> Option<bool> { + let Some(OverviewProgress::Gesture(gesture)) = &mut self.overview_progress else { + return None; + }; + + gesture.tracker.push(delta_y, timestamp); + + let total_height = OVERVIEW_GESTURE_MOVEMENT; + let pos = gesture.tracker.pos() / total_height; + let new_value = gesture.start + pos; + let new_value = OVERVIEW_GESTURE_RUBBER_BAND.clamp(0., 1., new_value); + + if gesture.value == new_value { + return Some(false); + } + + gesture.value = new_value; + self.set_monitors_overview_state(); + + Some(true) + } + + pub fn overview_gesture_end(&mut self) -> bool { + let Some(OverviewProgress::Gesture(gesture)) = &mut self.overview_progress else { + return false; + }; + + // Take into account any idle time between the last event and now. + let now = self.clock.now_unadjusted(); + gesture.tracker.push(0., now); + + let total_height = OVERVIEW_GESTURE_MOVEMENT; + + let mut velocity = gesture.tracker.velocity() / total_height; + let current_pos = gesture.tracker.pos() / total_height; + let pos = gesture.tracker.projected_end_pos() / total_height; + + let new_value = gesture.start + pos; + let new_value = new_value.clamp(0., 1.).round(); + + velocity *= + OVERVIEW_GESTURE_RUBBER_BAND.clamp_derivative(0., 1., gesture.start + current_pos); + + self.overview_open = new_value == 1.; + self.overview_progress = Some(OverviewProgress::Animation(Animation::new( + self.clock.clone(), + gesture.value, + new_value, + velocity, + self.options.animations.overview_open_close.0, + ))); + + self.set_monitors_overview_state(); + + true + } + pub fn interactive_move_begin( &mut self, window_id: W::Id, @@ -3710,6 +3923,8 @@ impl<W: LayoutElement> Layout<W> { return false; } + let zoom = mon.overview_zoom(); + let is_floating = ws.is_floating(&window_id); let (tile, tile_offset, _visible) = ws .tiles_with_render_positions() @@ -3717,10 +3932,11 @@ impl<W: LayoutElement> Layout<W> { .unwrap(); let window_offset = tile.window_loc(); - let tile_pos = ws_geo.loc + tile_offset; + let tile_pos = ws_geo.loc + tile_offset.upscale(zoom); - let pointer_offset_within_window = start_pos_within_output - tile_pos - window_offset; - let window_size = tile.window_size(); + let pointer_offset_within_window = + start_pos_within_output - tile_pos - window_offset.upscale(zoom); + let window_size = tile.window_size().upscale(zoom); let pointer_ratio_within_window = ( f64::clamp(pointer_offset_within_window.x / window_size.w, 0., 1.), f64::clamp(pointer_offset_within_window.y / window_size.h, 0., 1.), @@ -3768,6 +3984,9 @@ impl<W: LayoutElement> Layout<W> { return false; } + let zoom = self.overview_zoom(); + let delta = delta.downscale(zoom); + pointer_delta += delta; let (cx, cy) = (pointer_delta.x, pointer_delta.y); @@ -3824,7 +4043,8 @@ impl<W: LayoutElement> Layout<W> { .find(|(tile, _, _)| tile.window().id() == window) .unwrap(); - tile_pos = Some(ws_geo.loc + tile_offset); + let zoom = mon.overview_zoom(); + tile_pos = Some((ws_geo.loc + tile_offset.upscale(zoom), zoom)); } } } @@ -3903,9 +4123,10 @@ impl<W: LayoutElement> Layout<W> { pointer_ratio_within_window, }; - if let Some(tile_pos) = tile_pos { - let new_tile_pos = data.tile_render_location(); - data.tile.animate_move_from(tile_pos - new_tile_pos); + if let Some((tile_pos, zoom)) = tile_pos { + let new_tile_pos = data.tile_render_location(zoom); + data.tile + .animate_move_from((tile_pos - new_tile_pos).downscale(zoom)); } self.interactive_move = Some(InteractiveMoveState::Moving(data)); @@ -3960,12 +4181,16 @@ impl<W: LayoutElement> Layout<W> { unreachable!() }; + let mut ws_id = None; for ws in self.workspaces_mut() { + let id = ws.id(); if let Some(tile) = ws.tiles_mut().find(|tile| *tile.window().id() == window_id) { let offset = tile.interactive_move_offset; tile.interactive_move_offset = Point::from((0., 0.)); tile.animate_move_from(offset); + + ws_id = Some(id); } // Unlock the view on the workspaces, but if the moved window was active, @@ -3980,6 +4205,32 @@ impl<W: LayoutElement> Layout<W> { } } + // In the overview, we want to click on a window to focus it, and also to + // click-and-drag to move the window. The way we handle this is by always starting + // the interactive move (to get frozen view), then, when in the overview, *not* + // calling interactive_move_update() until the cursor moves far enough. This means + // that if we "just click" then we end up in this branch with state == Starting. + // Close the overview in this case. + if self.overview_open { + let ws_id = ws_id.unwrap(); + if let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set { + for mon in monitors { + if let Some(ws_idx) = + mon.workspaces.iter().position(|ws| ws.id() == ws_id) + { + mon.activate_workspace_with_anim_config( + ws_idx, + Some(self.options.animations.overview_open_close.0), + ); + break; + } + } + } + + self.activate_window(&window_id); + self.close_overview(); + } + return; } InteractiveMoveState::Moving(move_) => move_, @@ -4007,20 +4258,28 @@ impl<W: LayoutElement> Layout<W> { ); } + // Dragging in the overview shouldn't switch the workspace and so on. + let activate = if self.overview_open { + ActivateWindow::No + } else { + ActivateWindow::Yes + }; + match &mut self.monitor_set { MonitorSet::Normal { monitors, active_monitor_idx, .. } => { - let (mon, ws_idx, position, offset) = if let Some(mon) = + let (mon, ws_idx, position, offset, zoom) = if let Some(mon) = monitors.iter_mut().find(|mon| mon.output == move_.output) { + let zoom = mon.overview_zoom(); + let (ws, ws_geo) = mon .workspace_under(move_.pointer_pos_within_output) // If the pointer is somehow outside the move output and a workspace switch - // is in progress, this won't necessarily do the expected thing, but also - // that is not really supposed to happen so eh? + // is in progress, this won't necessarily do the expected thing. .unwrap_or_else(|| mon.workspaces_with_render_geo().next().unwrap()); let ws_id = ws.id(); @@ -4033,14 +4292,16 @@ impl<W: LayoutElement> Layout<W> { let position = if move_.is_floating { InsertPosition::Floating } else { - let pos_within_workspace = move_.pointer_pos_within_output - ws_geo.loc; + let pos_within_workspace = + (move_.pointer_pos_within_output - ws_geo.loc).downscale(zoom); let ws = &mut mon.workspaces[ws_idx]; ws.scrolling_insert_position(pos_within_workspace) }; - (mon, ws_idx, position, ws_geo.loc) + (mon, ws_idx, position, ws_geo.loc, zoom) } else { let mon = &mut monitors[*active_monitor_idx]; + let zoom = mon.overview_zoom(); // No point in trying to use the pointer position on the wrong output. let (ws, ws_geo) = mon.workspaces_with_render_geo().next().unwrap(); @@ -4056,11 +4317,12 @@ impl<W: LayoutElement> Layout<W> { .iter_mut() .position(|ws| ws.id() == ws_id) .unwrap(); - (mon, ws_idx, position, ws_geo.loc) + + (mon, ws_idx, position, ws_geo.loc, zoom) }; let win_id = move_.tile.window().id().clone(); - let window_render_loc = move_.tile_render_location() + move_.tile.window_loc(); + let window_render_loc = move_.tile_render_location(zoom) + move_.tile.window_loc(); match position { InsertPosition::NewColumn(column_idx) => { @@ -4071,7 +4333,7 @@ impl<W: LayoutElement> Layout<W> { id: ws_id, column_idx: Some(column_idx), }, - ActivateWindow::Yes, + activate, move_.width, move_.is_full_width, false, @@ -4083,13 +4345,15 @@ impl<W: LayoutElement> Layout<W> { column_idx, Some(tile_idx), move_.tile, - true, + activate == ActivateWindow::Yes, ); } InsertPosition::Floating => { - let pos = move_.tile_render_location() - offset; + let tile_render_loc = move_.tile_render_location(zoom); let mut tile = move_.tile; + + let pos = (tile_render_loc - offset).downscale(zoom); let pos = mon.workspaces[ws_idx].floating_logical_to_size_frac(pos); tile.floating_pos = Some(pos); @@ -4106,7 +4370,7 @@ impl<W: LayoutElement> Layout<W> { id: ws_id, column_idx: None, }, - ActivateWindow::Yes, + activate, move_.width, move_.is_full_width, true, @@ -4115,15 +4379,18 @@ impl<W: LayoutElement> Layout<W> { } // needed because empty_workspace_above_first could have modified the idx - let ws_idx = mon.active_workspace_idx(); - let ws = &mut mon.workspaces[ws_idx]; - let (tile, tile_render_loc) = ws - .tiles_with_render_positions_mut(false) - .find(|(tile, _)| tile.window().id() == &win_id) + let (tile, tile_render_loc, ws_geo) = mon + .workspaces_with_render_geo_mut() + .find_map(|(ws, geo)| { + ws.tiles_with_render_positions_mut(false) + .find(|(tile, _)| tile.window().id() == &win_id) + .map(|(tile, tile_render_loc)| (tile, tile_render_loc, geo)) + }) .unwrap(); - let new_window_render_loc = offset + tile_render_loc + tile.window_loc(); + let new_window_render_loc = + ws_geo.loc + (tile_render_loc + tile.window_loc()).upscale(zoom); - tile.animate_move_from(window_render_loc - new_window_render_loc); + tile.animate_move_from((window_render_loc - new_window_render_loc).downscale(zoom)); } MonitorSet::NoOutputs { workspaces, .. } => { if workspaces.is_empty() { @@ -4138,7 +4405,7 @@ impl<W: LayoutElement> Layout<W> { ws.add_tile( move_.tile, WorkspaceAddWindowTarget::Auto, - ActivateWindow::Yes, + activate, move_.width, move_.is_full_width, move_.is_floating, @@ -4376,6 +4643,60 @@ impl<W: LayoutElement> Layout<W> { self.unname_workspace_by_id(id); } + pub fn set_monitors_overview_state(&mut self) { + let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else { + return; + }; + + for mon in monitors { + mon.overview_open = self.overview_open; + mon.set_overview_progress(self.overview_progress.as_ref()); + } + } + + pub fn toggle_overview(&mut self) { + self.overview_open = !self.overview_open; + + let from = self.overview_progress.take().map_or(0., |p| p.value()); + let to = if self.overview_open { 1. } else { 0. }; + + self.overview_progress = Some(OverviewProgress::Animation(Animation::new( + self.clock.clone(), + from, + to, + 0., + self.options.animations.overview_open_close.0, + ))); + + self.set_monitors_overview_state(); + } + + pub fn open_overview(&mut self) -> bool { + if self.overview_open { + return false; + } + + self.toggle_overview(); + true + } + + pub fn close_overview(&mut self) -> bool { + if !self.overview_open { + return false; + } + + self.toggle_overview(); + true + } + + pub fn toggle_overview_to_workspace(&mut self, ws_idx: usize) { + let config = self.options.animations.overview_open_close.0; + if let Some(mon) = self.active_monitor() { + mon.activate_workspace_with_anim_config(ws_idx, Some(config)); + } + self.toggle_overview(); + } + pub fn start_open_animation_for_window(&mut self, window: &W::Id) { if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { if move_.tile.window().id() == window { @@ -4460,12 +4781,14 @@ impl<W: LayoutElement> Layout<W> { ) { let _span = tracy_client::span!("Layout::start_close_animation_for_window"); + let zoom = self.overview_zoom(); + if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { if move_.tile.window().id() == window { let Some(snapshot) = move_.tile.take_unmap_snapshot() else { return; }; - let tile_pos = move_.tile_render_location(); + let tile_pos = move_.tile_render_location(zoom); let tile_size = move_.tile.tile_size(); let output = move_.output.clone(); @@ -4516,7 +4839,7 @@ impl<W: LayoutElement> Layout<W> { renderer: &mut R, output: &Output, target: RenderTarget, - ) -> impl Iterator<Item = TileRenderElement<R>> + 'a { + ) -> impl Iterator<Item = RescaleRenderElement<TileRenderElement<R>>> + 'a { if self.update_render_elements_time != self.clock.now() { error!("clock moved between updating render elements and rendering"); } @@ -4525,8 +4848,20 @@ impl<W: LayoutElement> Layout<W> { if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { if &move_.output == output { - let location = move_.tile_render_location(); - rv = Some(move_.tile.render(renderer, location, true, target)); + let scale = Scale::from(move_.output.current_scale().fractional_scale()); + let zoom = self.overview_zoom(); + let location = move_.tile_render_location(zoom); + let iter = move_ + .tile + .render(renderer, location, true, target) + .map(move |elem| { + RescaleRenderElement::from_element( + elem, + location.to_physical_precise_round(scale), + zoom, + ) + }); + rv = Some(iter); } } @@ -4589,7 +4924,7 @@ impl<W: LayoutElement> Layout<W> { } } else { // Cancel the view offset gesture after workspace switches, moves, etc. - if ws_idx != mon.active_workspace_idx { + if !self.overview_open && ws_idx != mon.active_workspace_idx { ws.view_offset_gesture_end(None); } } @@ -4684,6 +5019,10 @@ impl<W: LayoutElement> Layout<W> { self.windows().any(|(_, win)| win.id() == window) } + pub fn is_overview_open(&self) -> bool { + self.overview_open + } + fn resolve_scrolling_width(&self, window: &W, width: Option<PresetSize>) -> ColumnWidth { let width = width.unwrap_or_else(|| PresetSize::Fixed(window.size().w)); match width { @@ -4709,3 +5048,14 @@ impl<W: LayoutElement> Default for MonitorSet<W> { Self::NoOutputs { workspaces: vec![] } } } + +fn compute_overview_zoom(options: &Options, overview_progress: Option<f64>) -> f64 { + // Clamp to some sane values. + let zoom = options.overview.zoom.0.clamp(0.0001, 0.75); + + if let Some(p) = overview_progress { + (1. - p * (1. - zoom)).max(0.0001) + } else { + 1. + } +} |
