diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index b2e5fcd1..2c4449af 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -3,6 +3,7 @@ use crate::shell::{CosmicMappedRenderElement, WorkspaceRenderElement}; use smithay::{ backend::renderer::{ element::{ + memory::MemoryRenderBufferRenderElement, surface::WaylandSurfaceRenderElement, texture::TextureRenderElement, utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement}, @@ -36,6 +37,7 @@ where RelocateRenderElement>>, >, ), + Zoom(MemoryRenderBufferRenderElement), #[cfg(feature = "debug")] Egui(TextureRenderElement), } @@ -54,6 +56,7 @@ where CosmicElement::MoveGrab(elem) => elem.id(), CosmicElement::AdditionalDamage(elem) => elem.id(), CosmicElement::Mirror(elem) => elem.id(), + CosmicElement::Zoom(elem) => elem.id(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.id(), } @@ -67,6 +70,7 @@ where CosmicElement::MoveGrab(elem) => elem.current_commit(), CosmicElement::AdditionalDamage(elem) => elem.current_commit(), CosmicElement::Mirror(elem) => elem.current_commit(), + CosmicElement::Zoom(elem) => elem.current_commit(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.current_commit(), } @@ -80,6 +84,7 @@ where CosmicElement::MoveGrab(elem) => elem.src(), CosmicElement::AdditionalDamage(elem) => elem.src(), CosmicElement::Mirror(elem) => elem.src(), + CosmicElement::Zoom(elem) => elem.src(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.src(), } @@ -93,6 +98,7 @@ where CosmicElement::MoveGrab(elem) => elem.geometry(scale), CosmicElement::AdditionalDamage(elem) => elem.geometry(scale), CosmicElement::Mirror(elem) => elem.geometry(scale), + CosmicElement::Zoom(elem) => elem.geometry(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.geometry(scale), } @@ -106,6 +112,7 @@ where CosmicElement::MoveGrab(elem) => elem.location(scale), CosmicElement::AdditionalDamage(elem) => elem.location(scale), CosmicElement::Mirror(elem) => elem.location(scale), + CosmicElement::Zoom(elem) => elem.location(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.location(scale), } @@ -119,6 +126,7 @@ where CosmicElement::MoveGrab(elem) => elem.transform(), CosmicElement::AdditionalDamage(elem) => elem.transform(), CosmicElement::Mirror(elem) => elem.transform(), + CosmicElement::Zoom(elem) => elem.transform(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.transform(), } @@ -136,6 +144,7 @@ where CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit), CosmicElement::AdditionalDamage(elem) => elem.damage_since(scale, commit), CosmicElement::Mirror(elem) => elem.damage_since(scale, commit), + CosmicElement::Zoom(elem) => elem.damage_since(scale, commit), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.damage_since(scale, commit), } @@ -149,6 +158,7 @@ where CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale), CosmicElement::AdditionalDamage(elem) => elem.opaque_regions(scale), CosmicElement::Mirror(elem) => elem.opaque_regions(scale), + CosmicElement::Zoom(elem) => elem.opaque_regions(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.opaque_regions(scale), } @@ -162,6 +172,7 @@ where CosmicElement::MoveGrab(elem) => elem.alpha(), CosmicElement::AdditionalDamage(elem) => elem.alpha(), CosmicElement::Mirror(elem) => elem.alpha(), + CosmicElement::Zoom(elem) => elem.alpha(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.alpha(), } @@ -175,6 +186,7 @@ where CosmicElement::MoveGrab(elem) => elem.kind(), CosmicElement::AdditionalDamage(elem) => elem.kind(), CosmicElement::Mirror(elem) => elem.kind(), + CosmicElement::Zoom(elem) => elem.kind(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.kind(), } @@ -219,6 +231,7 @@ where }; elem } + CosmicElement::Zoom(elem) => elem.draw(frame, src, dst, damage, opaque_regions), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { let elem = { @@ -254,6 +267,7 @@ where _ => None, } } + CosmicElement::Zoom(elem) => elem.underlying_storage(renderer), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { let glow_renderer = renderer.glow_renderer_mut(); @@ -295,6 +309,17 @@ where } } +impl From> for CosmicElement +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: 'static, + CosmicMappedRenderElement: RenderElement, +{ + fn from(value: MemoryRenderBufferRenderElement) -> Self { + Self::Zoom(value) + } +} + #[cfg(feature = "debug")] impl From> for CosmicElement where diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index ae2a596f..f24ba523 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -18,6 +18,7 @@ use crate::{ focus::{render_input_order, target::WindowGroup, Stage}, grabs::{SeatMenuGrabState, SeatMoveGrabState}, layout::tiling::ANIMATION_DURATION, + zoom::ZoomState, CosmicMappedRenderElement, OverviewMode, SeatExt, Trigger, WorkspaceDelta, WorkspaceRenderElement, }, @@ -404,7 +405,7 @@ pub enum CursorMode { pub fn cursor_elements<'a, 'frame, R>( renderer: &mut R, seats: impl Iterator>, - zoom_level: Option<(Seat, Point, f64)>, + zoom_state: Option<&ZoomState>, theme: &Theme, now: Time, output: &Output, @@ -417,8 +418,13 @@ where CosmicMappedRenderElement: RenderElement, { let scale = output.current_scale().fractional_scale(); - let (focal_point, zoom_scale) = zoom_level - .map(|(_, p, s)| (p.to_local(&output), s)) + let (focal_point, zoom_scale) = zoom_state + .map(|state| { + ( + state.focal_point(Some(&output)).to_local(&output), + state.animating_level(), + ) + }) .unwrap_or_else(|| ((0., 0.).into(), 1.)); let mut elements = Vec::new(); @@ -495,23 +501,32 @@ where })); } - if let Some(grab_elements) = seat + if let Some((grab_elements, should_scale)) = seat .user_data() .get::() .unwrap() .lock() .unwrap() .as_ref() - .map(|state| state.render::, R>(renderer, output)) + .map(|state| { + ( + state.render::, R>(renderer, output), + !state.is_in_screen_space(), + ) + }) { elements.extend(grab_elements.into_iter().map(|elem| { CosmicElement::MoveGrab(RescaleRenderElement::from_element( elem, - focal_point - .as_logical() - .to_physical(output.current_scale().fractional_scale()) - .to_i32_round(), - zoom_scale, + if should_scale { + focal_point + .as_logical() + .to_physical(output.current_scale().fractional_scale()) + .to_i32_round() + } else { + Point::from((0, 0)) + }, + if should_scale { zoom_scale } else { 1.0 }, )) })); } @@ -595,14 +610,14 @@ where } else { ElementFilter::All }; - let zoom_level = shell.read().unwrap().zoom_level(Some(&output)); + let zoom_state = shell.read().unwrap().zoom_state().cloned(); #[allow(unused_mut)] let workspace_elements = workspace_elements( _gpu, renderer, shell, - zoom_level, + zoom_state.as_ref(), now, output, previous_workspace, @@ -625,7 +640,7 @@ pub fn workspace_elements( _gpu: Option<&DrmNode>, renderer: &mut R, shell: &Arc>, - zoom_level: Option<(Seat, Point, f64)>, + zoom_level: Option<&ZoomState>, now: Time, output: &Output, previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>, @@ -642,18 +657,16 @@ where { let mut elements = Vec::new(); - let theme = shell.read().unwrap().theme().clone(); - let seats = shell - .read() - .unwrap() - .seats - .iter() - .cloned() - .collect::>(); + let shell_ref = shell.read().unwrap(); + let seats = shell_ref.seats.iter().cloned().collect::>(); if seats.is_empty() { return Ok(Vec::new()); } + let theme = shell_ref.theme().clone(); let scale = output.current_scale().fractional_scale(); + // we don't want to hold a shell lock across `cursor_elements`, + // that is prone to deadlock with the main-thread on some grabs. + std::mem::drop(shell_ref); elements.extend(cursor_elements( renderer, @@ -667,7 +680,6 @@ where )); let shell = shell.read().unwrap(); - let overview = shell.overview_mode(); let (resize_mode, resize_indicator) = shell.resize_mode(); let resize_indicator = resize_indicator.map(|indicator| (resize_mode, indicator)); @@ -715,7 +727,12 @@ where .as_logical() .to_physical_precise_round(scale); let (focal_point, zoom_scale) = zoom_level - .map(|(_, p, s)| (p.to_local(&output), s)) + .map(|state| { + ( + state.focal_point(Some(&output)).to_local(&output), + state.animating_level(), + ) + }) .unwrap_or_else(|| ((0., 0.).into(), 1.)); let crop_to_output = |element: WorkspaceRenderElement| { @@ -741,6 +758,9 @@ where element_filter, |stage| { match stage { + Stage::ZoomUI => { + elements.extend(ZoomState::render(renderer, output)); + } Stage::SessionLock(lock_surface) => { elements.extend( session_lock_elements(renderer, output, lock_surface) @@ -1006,6 +1026,7 @@ where .zip(previous_idx) .map(|((w, start), idx)| (w.handle, idx, start)); let workspace = (workspace.handle, idx); + let zoom_state = shell_ref.zoom_state().cloned(); std::mem::drop(shell_ref); let element_filter = if workspace_overview_is_open(output) { @@ -1013,7 +1034,6 @@ where } else { ElementFilter::All }; - let zoom_level = shell.read().unwrap().zoom_level(Some(&output)); let result = render_workspace( gpu, @@ -1023,7 +1043,7 @@ where age, None, shell, - zoom_level, + zoom_state.as_ref(), now, output, previous_workspace, @@ -1136,7 +1156,7 @@ pub fn render_workspace<'d, R, Target, OffTarget>( age: usize, additional_damage: Option>>, shell: &Arc>, - zoom_level: Option<(Seat, Point, f64)>, + zoom_level: Option<&ZoomState>, now: Time, output: &Output, previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>, diff --git a/src/debug.rs b/src/debug.rs index 989bee6a..dd88f211 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -323,6 +323,7 @@ fn format_pointer_focus(focus: Option) -> String { window.surface().title() ), Some(ResizeFork(_)) => String::from("Resize UI"), + Some(ZoomUI(_)) => String::from("Zoom UI"), None => format!("None"), } } diff --git a/src/input/mod.rs b/src/input/mod.rs index d719f5c0..2ca5dedb 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -21,6 +21,7 @@ use crate::{ floating::ResizeGrabMarker, tiling::{NodeDesc, SwapWindowGrab, TilingLayout}, }, + zoom::ZoomState, SeatExt, Trigger, }, utils::{float::NextDown, prelude::*, quirks::workspace_overview_is_open}, @@ -1187,7 +1188,8 @@ impl State { return; }; - let position = transform_output_mapped_position(&output, &event); + let position = + transform_output_mapped_position(&output, &event, shell.zoom_state()); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1218,7 +1220,8 @@ impl State { return; }; - let position = transform_output_mapped_position(&output, &event); + let position = + transform_output_mapped_position(&output, &event, shell.zoom_state()); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1302,7 +1305,8 @@ impl State { return; }; - let position = transform_output_mapped_position(&output, &event); + let position = + transform_output_mapped_position(&output, &event, shell.zoom_state()); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1366,7 +1370,8 @@ impl State { return; }; - let position = transform_output_mapped_position(&output, &event); + let position = + transform_output_mapped_position(&output, &event, shell.zoom_state()); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1939,6 +1944,7 @@ impl State { element_filter, |stage| { match stage { + Stage::ZoomUI => {} Stage::SessionLock(lock_surface) => { return ControlFlow::Break(Ok(lock_surface .cloned() @@ -2067,6 +2073,15 @@ impl State { element_filter, |stage| { match stage { + Stage::ZoomUI => { + if let Some(zoom_state) = shell.zoom_state() { + if let Some((target, loc)) = + zoom_state.surface_under(output, global_pos) + { + return ControlFlow::Break(Ok(Some((target, loc)))); + } + } + } Stage::SessionLock(lock_surface) => { return ControlFlow::Break(Ok(lock_surface.map(|surface| { ( @@ -2196,13 +2211,19 @@ fn cursor_sessions_for_output<'a>( }) } -fn transform_output_mapped_position<'a, B, E>(output: &Output, event: &E) -> Point +fn transform_output_mapped_position<'a, B, E>( + output: &Output, + event: &E, + zoom_state: Option<&ZoomState>, +) -> Point where B: InputBackend, E: AbsolutePositionEvent, B::Device: 'static, { - let geometry = output.geometry(); + let geometry = zoom_state + .and_then(|state| output.zoomed_geometry(state.current_level())) + .unwrap_or_else(|| output.geometry()); let transform = output.current_transform(); let size = transform .invert() diff --git a/src/shell/focus/order.rs b/src/shell/focus/order.rs index e03eb607..b9390c11 100644 --- a/src/shell/focus/order.rs +++ b/src/shell/focus/order.rs @@ -21,6 +21,7 @@ use crate::{ }; pub enum Stage<'a> { + ZoomUI, SessionLock(Option<&'a LockSurface>), LayerPopup { layer: LayerSurface, @@ -69,6 +70,10 @@ fn render_input_order_internal( element_filter: ElementFilter, mut callback: impl FnMut(Stage) -> ControlFlow, ()>, ) -> ControlFlow, ()> { + if shell.zoom_state.is_some() { + callback(Stage::ZoomUI)?; + } + // Session Lock if let Some(session_lock) = &shell.session_lock { return callback(Stage::SessionLock(session_lock.surfaces.get(output))); diff --git a/src/shell/focus/target.rs b/src/shell/focus/target.rs index 176eff03..77e0c941 100644 --- a/src/shell/focus/target.rs +++ b/src/shell/focus/target.rs @@ -4,6 +4,7 @@ use crate::{ shell::{ element::{CosmicMapped, CosmicStack, CosmicWindow}, layout::tiling::ResizeForkTarget, + zoom::ZoomFocusTarget, CosmicSurface, SeatExt, }, utils::prelude::*, @@ -41,6 +42,7 @@ pub enum PointerFocusTarget { StackUI(CosmicStack), WindowUI(CosmicWindow), ResizeFork(ResizeForkTarget), + ZoomUI(ZoomFocusTarget), } #[derive(Debug, Clone, PartialEq)] @@ -188,6 +190,7 @@ impl IsAlive for PointerFocusTarget { PointerFocusTarget::StackUI(e) => e.alive(), PointerFocusTarget::WindowUI(e) => e.alive(), PointerFocusTarget::ResizeFork(f) => f.alive(), + PointerFocusTarget::ZoomUI(_) => true, } } } @@ -233,6 +236,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::StackUI(u) => PointerTarget::enter(u, seat, data, event), PointerFocusTarget::WindowUI(u) => PointerTarget::enter(u, seat, data, event), PointerFocusTarget::ResizeFork(f) => PointerTarget::enter(f, seat, data, event), + PointerFocusTarget::ZoomUI(e) => PointerTarget::enter(e, seat, data, event), } } fn motion(&self, seat: &Seat, data: &mut State, event: &PointerMotionEvent) { @@ -262,6 +266,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::StackUI(u) => PointerTarget::motion(u, seat, data, event), PointerFocusTarget::WindowUI(u) => PointerTarget::motion(u, seat, data, event), PointerFocusTarget::ResizeFork(f) => PointerTarget::motion(f, seat, data, event), + PointerFocusTarget::ZoomUI(e) => PointerTarget::motion(e, seat, data, event), } } fn relative_motion(&self, seat: &Seat, data: &mut State, event: &RelativeMotionEvent) { @@ -274,6 +279,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(f) => { PointerTarget::relative_motion(f, seat, data, event) } + PointerFocusTarget::ZoomUI(e) => PointerTarget::relative_motion(e, seat, data, event), } } fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { @@ -284,6 +290,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::StackUI(u) => PointerTarget::button(u, seat, data, event), PointerFocusTarget::WindowUI(u) => PointerTarget::button(u, seat, data, event), PointerFocusTarget::ResizeFork(f) => PointerTarget::button(f, seat, data, event), + PointerFocusTarget::ZoomUI(e) => PointerTarget::button(e, seat, data, event), } } fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { @@ -294,6 +301,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::StackUI(u) => PointerTarget::axis(u, seat, data, frame), PointerFocusTarget::WindowUI(u) => PointerTarget::axis(u, seat, data, frame), PointerFocusTarget::ResizeFork(f) => PointerTarget::axis(f, seat, data, frame), + PointerFocusTarget::ZoomUI(e) => PointerTarget::axis(e, seat, data, frame), } } fn frame(&self, seat: &Seat, data: &mut State) { @@ -304,6 +312,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::StackUI(u) => PointerTarget::frame(u, seat, data), PointerFocusTarget::WindowUI(u) => PointerTarget::frame(u, seat, data), PointerFocusTarget::ResizeFork(f) => PointerTarget::frame(f, seat, data), + PointerFocusTarget::ZoomUI(e) => PointerTarget::frame(e, seat, data), } } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { @@ -322,6 +331,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::StackUI(u) => PointerTarget::leave(u, seat, data, serial, time), PointerFocusTarget::WindowUI(u) => PointerTarget::leave(u, seat, data, serial, time), PointerFocusTarget::ResizeFork(f) => PointerTarget::leave(f, seat, data, serial, time), + PointerFocusTarget::ZoomUI(e) => PointerTarget::leave(e, seat, data, serial, time), } } fn gesture_swipe_begin( @@ -343,6 +353,9 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(f) => { PointerTarget::gesture_swipe_begin(f, seat, data, event) } + PointerFocusTarget::ZoomUI(e) => { + PointerTarget::gesture_swipe_begin(e, seat, data, event) + } } } fn gesture_swipe_update( @@ -364,6 +377,9 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(f) => { PointerTarget::gesture_swipe_update(f, seat, data, event) } + PointerFocusTarget::ZoomUI(e) => { + PointerTarget::gesture_swipe_update(e, seat, data, event) + } } } fn gesture_swipe_end( @@ -385,6 +401,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(f) => { PointerTarget::gesture_swipe_end(f, seat, data, event) } + PointerFocusTarget::ZoomUI(e) => PointerTarget::gesture_swipe_end(e, seat, data, event), } } fn gesture_pinch_begin( @@ -406,6 +423,9 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(f) => { PointerTarget::gesture_pinch_begin(f, seat, data, event) } + PointerFocusTarget::ZoomUI(e) => { + PointerTarget::gesture_pinch_begin(e, seat, data, event) + } } } fn gesture_pinch_update( @@ -427,6 +447,9 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(f) => { PointerTarget::gesture_pinch_update(f, seat, data, event) } + PointerFocusTarget::ZoomUI(e) => { + PointerTarget::gesture_pinch_update(e, seat, data, event) + } } } fn gesture_pinch_end( @@ -448,6 +471,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(f) => { PointerTarget::gesture_pinch_end(f, seat, data, event) } + PointerFocusTarget::ZoomUI(e) => PointerTarget::gesture_pinch_end(e, seat, data, event), } } fn gesture_hold_begin( @@ -469,6 +493,9 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(f) => { PointerTarget::gesture_hold_begin(f, seat, data, event) } + PointerFocusTarget::ZoomUI(e) => { + PointerTarget::gesture_hold_begin(e, seat, data, event) + } } } fn gesture_hold_end(&self, seat: &Seat, data: &mut State, event: &GestureHoldEndEvent) { @@ -483,6 +510,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(f) => { PointerTarget::gesture_hold_end(f, seat, data, event) } + PointerFocusTarget::ZoomUI(e) => PointerTarget::gesture_hold_end(e, seat, data, event), } } } @@ -498,6 +526,7 @@ impl TouchTarget for PointerFocusTarget { } PointerFocusTarget::StackUI(stack) => TouchTarget::down(stack, seat, data, event, seq), PointerFocusTarget::ResizeFork(fork) => TouchTarget::down(fork, seat, data, event, seq), + PointerFocusTarget::ZoomUI(elem) => TouchTarget::down(elem, seat, data, event, seq), } } @@ -509,6 +538,7 @@ impl TouchTarget for PointerFocusTarget { PointerFocusTarget::WindowUI(window) => TouchTarget::up(window, seat, data, event, seq), PointerFocusTarget::StackUI(stack) => TouchTarget::up(stack, seat, data, event, seq), PointerFocusTarget::ResizeFork(fork) => TouchTarget::up(fork, seat, data, event, seq), + PointerFocusTarget::ZoomUI(elem) => TouchTarget::up(elem, seat, data, event, seq), } } @@ -526,6 +556,7 @@ impl TouchTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(fork) => { TouchTarget::motion(fork, seat, data, event, seq) } + PointerFocusTarget::ZoomUI(elem) => TouchTarget::motion(elem, seat, data, event, seq), } } @@ -537,6 +568,7 @@ impl TouchTarget for PointerFocusTarget { PointerFocusTarget::WindowUI(window) => TouchTarget::frame(window, seat, data, seq), PointerFocusTarget::StackUI(stack) => TouchTarget::frame(stack, seat, data, seq), PointerFocusTarget::ResizeFork(fork) => TouchTarget::frame(fork, seat, data, seq), + PointerFocusTarget::ZoomUI(elem) => TouchTarget::frame(elem, seat, data, seq), } } @@ -548,6 +580,7 @@ impl TouchTarget for PointerFocusTarget { PointerFocusTarget::WindowUI(window) => TouchTarget::cancel(window, seat, data, seq), PointerFocusTarget::StackUI(stack) => TouchTarget::cancel(stack, seat, data, seq), PointerFocusTarget::ResizeFork(fork) => TouchTarget::cancel(fork, seat, data, seq), + PointerFocusTarget::ZoomUI(elem) => TouchTarget::cancel(elem, seat, data, seq), } } @@ -563,6 +596,7 @@ impl TouchTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(fork) => { TouchTarget::shape(fork, seat, data, event, seq) } + PointerFocusTarget::ZoomUI(elem) => TouchTarget::shape(elem, seat, data, event, seq), } } @@ -586,6 +620,9 @@ impl TouchTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(fork) => { TouchTarget::orientation(fork, seat, data, event, seq) } + PointerFocusTarget::ZoomUI(elem) => { + TouchTarget::orientation(elem, seat, data, event, seq) + } } } } @@ -734,7 +771,8 @@ impl WaylandFocus for PointerFocusTarget { PointerFocusTarget::WlSurface { surface, .. } => Cow::Borrowed(surface), PointerFocusTarget::ResizeFork(_) | PointerFocusTarget::StackUI(_) - | PointerFocusTarget::WindowUI(_) => { + | PointerFocusTarget::WindowUI(_) + | PointerFocusTarget::ZoomUI(_) => { return None; } }) @@ -751,7 +789,7 @@ impl WaylandFocus for PointerFocusTarget { .wl_surface() .map(|s| s.id().same_client_as(object_id)) .unwrap_or(false), - PointerFocusTarget::ResizeFork(_) => false, + PointerFocusTarget::ResizeFork(_) | PointerFocusTarget::ZoomUI(_) => false, } } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index ae95559c..91e4d6ff 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -10,10 +10,7 @@ use std::{ }; use wayland_backend::server::ClientId; -use crate::{ - utils::{float::NextDown, tween::EasePoint}, - wayland::{handlers::data_device, protocols::workspace::WorkspaceCapabilities}, -}; +use crate::wayland::{handlers::data_device, protocols::workspace::WorkspaceCapabilities}; use cosmic_comp_config::{ workspace::{WorkspaceLayout, WorkspaceMode}, TileBehavior, ZoomConfig, ZoomMovement, @@ -85,9 +82,11 @@ pub mod grabs; pub mod layout; mod seats; mod workspace; +pub mod zoom; pub use self::element::{CosmicMapped, CosmicMappedRenderElement, CosmicSurface}; pub use self::seats::*; pub use self::workspace::*; +use self::zoom::{OutputZoomState, ZoomState}; use self::{ element::{ @@ -248,116 +247,6 @@ pub struct PendingLayer { pub output: Output, } -struct ZoomState { - seat: Seat, - level: f64, - movement: ZoomMovement, - previous_level: Option<(f64, Instant)>, -} - -#[derive(Debug)] -struct OutputZoomState { - focal_point: Point, - previous_point: Option<(Point, Instant)>, -} - -impl OutputZoomState { - pub fn new( - seat: &Seat, - output: &Output, - movement: ZoomMovement, - level: f64, - ) -> OutputZoomState { - let cursor_position = seat.get_pointer().unwrap().current_location().as_global(); - let output_geometry = output.geometry().to_f64(); - let focal_point = if output_geometry.contains(cursor_position) { - match movement { - ZoomMovement::Continuously | ZoomMovement::OnEdge => { - cursor_position.to_local(&output) - } - ZoomMovement::Centered => { - let mut zoomed_output_geometry = output_geometry; - zoomed_output_geometry = zoomed_output_geometry.downscale(level); - zoomed_output_geometry.loc = - cursor_position - zoomed_output_geometry.size.downscale(2.).to_point(); - - let mut focal_point = zoomed_output_geometry - .loc - .to_local(&output) - .upscale(level) - .to_global(&output); - focal_point.x = focal_point.x.clamp( - output_geometry.loc.x as f64, - ((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable - ); - focal_point.y = focal_point.y.clamp( - output_geometry.loc.y as f64, - ((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable - ); - focal_point.to_local(&output) - } - } - } else { - (output_geometry.size.w / 2., output_geometry.size.h / 2.).into() - }; - - OutputZoomState { - focal_point, - previous_point: None, - } - } - - fn focal_point(&mut self) -> Point { - if let Some((old_point, start)) = self.previous_point.as_ref() { - let duration_since = Instant::now().duration_since(*start); - if duration_since > ANIMATION_DURATION { - self.previous_point.take(); - return self.focal_point; - } - - let percentage = - duration_since.as_millis() as f32 / ANIMATION_DURATION.as_millis() as f32; - ease( - EaseInOutCubic, - EasePoint(*old_point), - EasePoint(self.focal_point), - percentage, - ) - .0 - } else { - self.focal_point - } - } -} - -impl ZoomState { - pub fn level(&self, output: Option<&Output>) -> (Seat, Point, f64) { - let active_output = self.seat.active_output(); - let output = output.unwrap_or(&active_output); - let output_state = output.user_data().get_or_insert_threadsafe(|| { - Mutex::new(OutputZoomState::new( - &self.seat, - output, - self.movement, - self.level, - )) - }); - let focal_point = output_state.lock().unwrap().focal_point().to_global(output); - - if let Some((old_level, start)) = self.previous_level.as_ref() { - let percentage = Instant::now().duration_since(*start).as_millis() as f32 - / ANIMATION_DURATION.as_millis() as f32; - ( - self.seat.clone(), - focal_point, - ease(EaseInOutCubic, *old_level, self.level, percentage), - ) - } else { - (self.seat.clone(), focal_point, self.level) - } - } -} - #[derive(Debug)] pub struct Shell { pub workspaces: Workspaces, @@ -1279,6 +1168,20 @@ impl Common { &self.xdg_activation_state, ); + if let Some(state) = shell.zoom_state.as_ref() { + output.user_data().insert_if_missing_threadsafe(|| { + Mutex::new(OutputZoomState::new( + &state.seat, + output, + state.level, + state.increment, + state.movement, + self.event_loop_handle.clone(), + shell.theme.clone(), + )) + }); + } + std::mem::drop(shell); self.refresh(); // fixes indicies of any moved workspaces } @@ -1941,18 +1844,8 @@ impl Shell { state.previous_level.is_some() || self.outputs().any(|o| { o.user_data() - .get_or_insert_threadsafe(|| { - Mutex::new(OutputZoomState::new( - &state.seat, - o, - state.movement, - state.level, - )) - }) - .lock() - .unwrap() - .previous_point - .is_some() + .get::>() + .is_some_and(|state| state.lock().unwrap().is_animating()) }) }) } @@ -2092,8 +1985,9 @@ impl Shell { &mut self, seat: &Seat, level: f64, - movement: ZoomMovement, + zoom_config: &ZoomConfig, animate: bool, + loop_handle: &LoopHandle<'static, State>, ) { if self.zoom_state.is_none() && level == 1. { return; @@ -2104,13 +1998,38 @@ impl Shell { return; } - old_state.level + for output in self.outputs() { + let output_state = output.user_data().get::>().unwrap(); + output_state.lock().unwrap().update( + level, + zoom_config.view_moves, + zoom_config.increment, + ); + } + + old_state.animating_level() } else { for output in self.outputs() { - if let Some(output_state) = output.user_data().get::>() { - *output_state.lock().unwrap() = - OutputZoomState::new(seat, output, movement, level); - } + let output_state = output.user_data().get_or_insert_threadsafe(|| { + Mutex::new(OutputZoomState::new( + seat, + output, + level, + zoom_config.increment, + zoom_config.view_moves, + loop_handle.clone(), + self.theme.clone(), + )) + }); + *output_state.lock().unwrap() = OutputZoomState::new( + seat, + output, + level, + zoom_config.increment, + zoom_config.view_moves, + loop_handle.clone(), + self.theme.clone(), + ); } 1. }; @@ -2118,7 +2037,8 @@ impl Shell { self.zoom_state = Some(ZoomState { seat: seat.clone(), level, - movement, + increment: zoom_config.increment, + movement: zoom_config.view_moves, previous_level: animate.then_some((previous_level, Instant::now())), }); } @@ -2134,103 +2054,19 @@ impl Shell { return; } - let output = seat.active_output(); - let output_state = output.user_data().get_or_insert_threadsafe(|| { - Mutex::new(OutputZoomState::new(seat, &output, movement, state.level)) - }); - let mut output_state_ref = output_state.lock().unwrap(); + let cursor_position = seat.get_pointer().unwrap().current_location().as_global(); - // animate movement type changes - if state.movement != movement { - output_state_ref.previous_point = - Some((output_state_ref.focal_point, Instant::now())); - state.movement = movement; - } - - let cursor_position = seat - .get_pointer() - .unwrap() - .current_location() - .as_global() - .to_local(&output); - match movement { - ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position, - ZoomMovement::OnEdge => { - let output_geometry = output.geometry().to_f64(); - let mut zoomed_output_geometry = output_geometry; - - zoomed_output_geometry.loc -= output_state_ref.focal_point.to_global(&output); - zoomed_output_geometry = zoomed_output_geometry.downscale(state.level); - zoomed_output_geometry.loc += output_state_ref.focal_point.to_global(&output); - - if !zoomed_output_geometry.contains(original_position) { - zoomed_output_geometry.loc = cursor_position.to_global(&output) - - zoomed_output_geometry.size.downscale(2.).to_point(); - let mut focal_point = zoomed_output_geometry - .loc - .to_local(&output) - .upscale(state.level) - .to_global(&output); - focal_point.x = focal_point.x.clamp( - output_geometry.loc.x as f64, - ((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable - ); - focal_point.y = focal_point.y.clamp( - output_geometry.loc.y as f64, - ((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable - ); - output_state_ref.previous_point = - Some((output_state_ref.focal_point, Instant::now())); - output_state_ref.focal_point = focal_point.to_local(&output); - } else if !zoomed_output_geometry.contains(cursor_position.to_global(&output)) { - let mut diff = output_state_ref.focal_point.to_global(&output) - + (cursor_position.to_global(&output) - original_position) - .upscale(state.level); - diff.x = diff.x.clamp( - output_geometry.loc.x as f64, - ((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable - ); - diff.y = diff.y.clamp( - output_geometry.loc.y as f64, - ((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable - ); - diff -= output_state_ref.focal_point.to_global(&output); - - output_state_ref.focal_point += diff.as_logical().as_local(); - } - } - ZoomMovement::Centered => { - let output_geometry = output.geometry().to_f64(); - - let mut zoomed_output_geometry = output_geometry; - zoomed_output_geometry = zoomed_output_geometry.downscale(state.level); - zoomed_output_geometry.loc = cursor_position.to_global(&output) - - zoomed_output_geometry.size.downscale(2.).to_point(); - - let mut focal_point = zoomed_output_geometry - .loc - .to_local(&output) - .upscale(state.level) - .to_global(&output); - focal_point.x = focal_point.x.clamp( - output_geometry.loc.x as f64, - ((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable - ); - focal_point.y = focal_point.y.clamp( - output_geometry.loc.y as f64, - ((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable - ); - output_state_ref.focal_point = focal_point.to_local(&output); - } - } + state.update_focal_point( + &seat.active_output(), + cursor_position, + original_position, + movement, + ); } } - pub fn zoom_level( - &self, - output: Option<&Output>, - ) -> Option<(Seat, Point, f64)> { - self.zoom_state.as_ref().map(|s| s.level(output)) + pub fn zoom_state(&self) -> Option<&ZoomState> { + self.zoom_state.as_ref() } fn refresh( @@ -2282,6 +2118,18 @@ impl Shell { if zoom_state.level == 1. && zoom_state.previous_level.is_none() { self.zoom_state.take(); } + + if self.zoom_state.is_some() { + for output in self.outputs() { + output + .user_data() + .get::>() + .unwrap() + .lock() + .unwrap() + .refresh(); + } + } } self.workspaces diff --git a/src/shell/zoom.rs b/src/shell/zoom.rs new file mode 100644 index 00000000..014ef11c --- /dev/null +++ b/src/shell/zoom.rs @@ -0,0 +1,999 @@ +use std::{sync::Mutex, time::Instant}; + +use calloop::LoopHandle; +use cosmic::{ + iced::{alignment::Vertical, Alignment, Background, Border, Length}, + iced_widget, theme, + widget::{self, icon::Named}, + Apply, +}; +use cosmic_comp_config::ZoomMovement; +use keyframe::{ease, functions::EaseInOutCubic}; +use smithay::{ + backend::renderer::{element::AsRenderElements, ImportMem, Renderer}, + desktop::space::SpaceElement, + input::{ + pointer::{ + AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent, + GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, + GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, + MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent, + }, + touch::{ + DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget, + UpEvent, + }, + Seat, + }, + output::Output, + utils::{IsAlive, Point, Rectangle, Serial, Size}, +}; + +use crate::{ + state::State, + utils::{ + float::NextDown, + iced::{IcedElement, Program}, + prelude::*, + tween::EasePoint, + }, +}; + +use super::{ + check_grab_preconditions, + focus::target::PointerFocusTarget, + grabs::{ContextMenu, Item, MenuAlignment, MenuGrab}, + ANIMATION_DURATION, +}; + +#[derive(Debug, Clone)] +pub struct ZoomState { + pub(super) seat: Seat, + pub(super) level: f64, + pub(super) increment: u32, + pub(super) movement: ZoomMovement, + pub(super) previous_level: Option<(f64, Instant)>, +} + +#[derive(Debug)] +pub struct OutputZoomState { + focal_point: Point, + previous_point: Option<(Point, Instant)>, + element: ZoomElement, +} + +impl OutputZoomState { + pub fn new( + seat: &Seat, + output: &Output, + level: f64, + increment: u32, + movement: ZoomMovement, + loop_handle: LoopHandle<'static, State>, + theme: cosmic::Theme, + ) -> OutputZoomState { + let cursor_position = seat.get_pointer().unwrap().current_location().as_global(); + let output_geometry = output.geometry().to_f64(); + let focal_point = if output_geometry.contains(cursor_position) { + match movement { + ZoomMovement::Continuously | ZoomMovement::OnEdge => { + cursor_position.to_local(&output) + } + ZoomMovement::Centered => { + let mut zoomed_output_geometry = + output.zoomed_geometry(level).unwrap().to_f64(); + zoomed_output_geometry.loc = + cursor_position - zoomed_output_geometry.size.downscale(2.).to_point(); + + let mut focal_point = zoomed_output_geometry + .loc + .to_local(&output) + .upscale(level) + .to_global(&output); + focal_point.x = focal_point.x.clamp( + output_geometry.loc.x as f64, + ((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable + ); + focal_point.y = focal_point.y.clamp( + output_geometry.loc.y as f64, + ((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable + ); + focal_point.to_local(&output) + } + } + } else { + (output_geometry.size.w / 2., output_geometry.size.h / 2.).into() + }; + + let program = ZoomProgram::new(level, movement, increment); + let element = IcedElement::new(program, Size::default(), loop_handle, theme); + let mut size = element.minimum_size(); + size.w = (size.w + 32/*TODO: figure out why iced is calculating too little*/) + .min(output_geometry.size.w.round() as i32); + element.set_activate(true); + element.resize(size); + element.output_enter(output, Rectangle::new(Point::from((0, 0)), size)); + element.refresh(); + + OutputZoomState { + focal_point, + previous_point: None, + element, + } + } + + pub fn focal_point(&mut self) -> Point { + if let Some((old_point, start)) = self.previous_point.as_ref() { + let duration_since = Instant::now().duration_since(*start); + if duration_since > ANIMATION_DURATION { + self.previous_point.take(); + return self.focal_point; + } + + let percentage = + duration_since.as_millis() as f32 / ANIMATION_DURATION.as_millis() as f32; + ease( + EaseInOutCubic, + EasePoint(*old_point), + EasePoint(self.focal_point), + percentage, + ) + .0 + } else { + self.focal_point + } + } + + pub fn is_animating(&self) -> bool { + self.previous_point.is_some() + } + + pub fn refresh(&self) { + self.element.refresh() + } + + pub fn update(&self, level: f64, movement: ZoomMovement, increment: u32) { + self.element.queue_message(ZoomMessage::Update { + level, + movement, + increment, + }); + } + + fn render(&mut self, renderer: &mut R, output: &Output) -> Vec + where + C: From< as AsRenderElements>::RenderElement>, + R: Renderer + ImportMem, + ::TextureId: Send + Clone + 'static, + { + let size = self.element.current_size().to_f64(); + let output_geo = output.geometry().to_f64(); + let scale = output.current_scale(); + let location = Point::from(( + output_geo.size.w / 2. - size.w / 2., + output_geo.size.h / 4. * 3. - size.h / 2., + )) + .to_physical(scale.fractional_scale()) + .to_i32_round(); + + self.element + .render_elements(renderer, location, scale.fractional_scale().into(), 1.0) + } +} + +impl ZoomState { + pub fn current_level(&self) -> f64 { + self.level + } + + pub fn animating_level(&self) -> f64 { + if let Some((old_level, start)) = self.previous_level.as_ref() { + let percentage = Instant::now().duration_since(*start).as_millis() as f32 + / ANIMATION_DURATION.as_millis() as f32; + + ease(EaseInOutCubic, *old_level, self.level, percentage) + } else { + self.level + } + } + + pub fn current_seat(&self) -> Seat { + self.seat.clone() + } + + pub fn focal_point(&self, output: Option<&Output>) -> Point { + let active_output = self.seat.active_output(); + let output = output.unwrap_or(&active_output); + let output_state = output.user_data().get::>().unwrap(); + let res = output_state.lock().unwrap().focal_point().to_global(output); + res + } + + pub fn update_focal_point( + &mut self, + output: &Output, + cursor_position: Point, + original_position: Point, + movement: ZoomMovement, + ) { + let output_geometry = output.geometry().to_f64(); + let mut zoomed_output_geometry = output.zoomed_geometry(self.level).unwrap().to_f64(); + + let output_state = output.user_data().get::>().unwrap(); + let mut output_state_ref = output_state.lock().unwrap(); + + // animate movement type changes + if self.movement != movement { + output_state_ref.previous_point = Some((output_state_ref.focal_point, Instant::now())); + self.movement = movement; + } + + let cursor_position = cursor_position.to_local(output); + match movement { + ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position, + ZoomMovement::OnEdge => { + if !zoomed_output_geometry.contains(original_position) { + zoomed_output_geometry.loc = cursor_position.to_global(&output) + - zoomed_output_geometry.size.downscale(2.).to_point(); + let mut focal_point = zoomed_output_geometry + .loc + .to_local(&output) + .upscale( + output_geometry.size.w + / (output_geometry.size.w - zoomed_output_geometry.size.w), + ) + .to_global(&output); + focal_point.x = focal_point.x.clamp( + output_geometry.loc.x as f64, + ((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable + ); + focal_point.y = focal_point.y.clamp( + output_geometry.loc.y as f64, + ((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable + ); + output_state_ref.previous_point = + Some((output_state_ref.focal_point, Instant::now())); + output_state_ref.focal_point = focal_point.to_local(&output); + } else if !zoomed_output_geometry.contains(cursor_position.to_global(&output)) { + let mut diff = output_state_ref.focal_point.to_global(&output) + + (cursor_position.to_global(&output) - original_position) + .upscale(self.level); + diff.x = diff.x.clamp( + output_geometry.loc.x as f64, + ((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable + ); + diff.y = diff.y.clamp( + output_geometry.loc.y as f64, + ((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable + ); + diff -= output_state_ref.focal_point.to_global(&output); + + output_state_ref.focal_point += diff.as_logical().as_local(); + } + } + ZoomMovement::Centered => { + zoomed_output_geometry.loc = cursor_position.to_global(&output) + - zoomed_output_geometry.size.downscale(2.).to_point(); + + let mut focal_point = zoomed_output_geometry + .loc + .to_local(&output) + .upscale( + output_geometry.size.w + / (output_geometry.size.w - zoomed_output_geometry.size.w), + ) + .to_global(&output); + focal_point.x = focal_point.x.clamp( + output_geometry.loc.x as f64, + ((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable + ); + focal_point.y = focal_point.y.clamp( + output_geometry.loc.y as f64, + ((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable + ); + output_state_ref.focal_point = focal_point.to_local(&output); + } + } + } + + pub fn surface_under( + &self, + output: &Output, + pos: Point, + ) -> Option<(PointerFocusTarget, Point)> { + let output_geometry = output.geometry(); + let zoomed_output_geometry = output.zoomed_geometry(self.level).unwrap().to_f64(); + + let local_pos = global_pos_to_screen_space(pos, output, self.level); + let output_state = output.user_data().get::>().unwrap(); + let output_state_ref = output_state.lock().unwrap(); + + let size = output_state_ref.element.current_size().to_f64().as_local(); + let location = Point::::from(( + output_geometry.size.w as f64 / 2. - size.w / 2., + output_geometry.size.h as f64 / 4. * 3. - size.h / 2., + )); + let area = Rectangle::<_, Local>::new(location, size); + + if area.contains(local_pos) { + return Some(( + PointerFocusTarget::ZoomUI(output_state_ref.element.clone().into()), + { + // and vise-versa from screen-space to zoom-space... + let scaled_loc = location.downscale(self.level); + let global_loc = Point::::from((scaled_loc.x, scaled_loc.y)) + + zoomed_output_geometry.loc; + + // HACK: We do have the right position now `global_loc`, but smithay calculates + // the relative position for us... Which will be wrong given the cursor movement will + // be scaled, while this element isn't, as it exists in screen-space and not workspace-space. + // So we shift the location relatively to make up for the scaled movement... + let diff = (pos - global_loc).upscale(self.level - 1.); + + global_loc - diff + }, + )); + } + + None + } + + pub fn render(renderer: &mut R, output: &Output) -> Vec + where + C: From< as AsRenderElements>::RenderElement>, + R: Renderer + ImportMem, + ::TextureId: Send + Clone + 'static, + { + let output_state = output.user_data().get::>().unwrap(); + output_state.lock().unwrap().render(renderer, output) + } +} + +fn global_pos_to_screen_space( + pos: impl Into>, + output: &Output, + level: f64, +) -> Point { + let pos = pos.into(); + let zoomed_output_geometry = output.zoomed_geometry(level).unwrap().to_f64(); + + // lets try to get the global cursor position into screen space + let relative_to_zoom_geo = Point::::from(( + pos.x - zoomed_output_geometry.loc.x, + pos.y - zoomed_output_geometry.loc.y, + )); + relative_to_zoom_geo.upscale(level) +} + +pub type ZoomElement = IcedElement; + +pub struct ZoomProgram { + level: f64, + increments: Vec, + increment_idx: usize, + movement: ZoomMovement, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ZoomMessage { + Decrease, + Increase, + Increment, + More, + Close, + Update { + level: f64, + increment: u32, + movement: ZoomMovement, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum MenuMessage { + ViewContinuously, + ViewOnEdge, + ViewCentered, + OpenSettings, +} + +impl ZoomProgram { + pub fn new(level: f64, movement: ZoomMovement, increment: u32) -> Self { + let mut increments = vec![25, 50, 100, 150, 200]; + if !increments.contains(&increment) { + increments.push(increment); + } + increments.sort(); + let increment_idx = increments.iter().position(|val| *val == increment).unwrap(); + + ZoomProgram { + level, + increments, + increment_idx, + movement, + } + } +} + +impl Program for ZoomProgram { + type Message = ZoomMessage; + + fn view(&self) -> cosmic::Element<'_, Self::Message> { + widget::row::with_children(vec![ + widget::button::icon(Named::new("list-remove-symbolic").size(16).prefer_svg(true)) + .on_press(ZoomMessage::Decrease) + .into(), + widget::text(format!("{}%", (self.level * 100.).round())) + .align_y(Vertical::Center) + .width(Length::Shrink) + .into(), + widget::button::icon(Named::new("list-add-symbolic").size(16).prefer_svg(true)) + .on_press(ZoomMessage::Increase) + .into(), + widget::divider::vertical::default().into(), + widget::button::text(format!("{}%", self.increments[self.increment_idx])) + .trailing_icon(Named::new("pan-down-symbolic").size(16).prefer_svg(true)) + .on_press(ZoomMessage::Increment) + .class(theme::Button::MenuFolder) + .into(), + widget::button::icon(Named::new("view-more-symbolic").size(16).prefer_svg(true)) + .on_press(ZoomMessage::More) + .into(), + widget::divider::vertical::default().into(), + widget::button::icon( + Named::new("window-close-symbolic") + .size(16) + .prefer_svg(true), + ) + .on_press(ZoomMessage::Close) + .into(), + ]) + .spacing(8.) + .height(Length::Fixed(32.)) + .width(Length::Shrink) + .align_y(Alignment::Center) + .apply(widget::container) + .padding(8) + .class(theme::Container::custom(|theme| { + let cosmic = theme.cosmic(); + let component = &cosmic.background.component; + iced_widget::container::Style { + icon_color: Some(component.on.into()), + text_color: Some(component.on.into()), + background: Some(Background::Color(component.base.into())), + border: Border { + radius: cosmic.radius_s().into(), + width: 1.0, + color: component.divider.into(), + }, + shadow: Default::default(), + } + })) + .into() + } + + fn update( + &mut self, + message: Self::Message, + loop_handle: &LoopHandle<'static, State>, + last_seat: Option<&(Seat, Serial)>, + ) -> cosmic::Task { + match message { + ZoomMessage::Decrease => { + let _ = loop_handle.insert_idle(|state| { + let seat = state + .common + .shell + .read() + .unwrap() + .seats + .last_active() + .clone(); + let increment = + state.common.config.cosmic_conf.accessibility_zoom.increment as f64 / 100.0; + + state.update_zoom(&seat, -increment, true); + }); + } + ZoomMessage::Increase => { + let _ = loop_handle.insert_idle(|state| { + let seat = state + .common + .shell + .read() + .unwrap() + .seats + .last_active() + .clone(); + let increment = + state.common.config.cosmic_conf.accessibility_zoom.increment as f64 / 100.0; + + state.update_zoom(&seat, increment, true); + }); + } + ZoomMessage::More => { + let movement = self.movement; + if let Some((seat, serial)) = last_seat.cloned() { + let _ = loop_handle.insert_idle(move |state| { + if let Some(start_data) = + check_grab_preconditions(&seat, Some(serial), None) + { + let shell = state.common.shell.read().unwrap(); + let output = seat.active_output(); + + if let Some(zoom_state) = shell.zoom_state() { + let location = global_pos_to_screen_space( + start_data.location().as_global(), + &output, + zoom_state.current_level(), + ); + + let output_geometry = output.geometry(); + let output_state = + output.user_data().get::>().unwrap(); + let output_state_ref = output_state.lock().unwrap(); + + let elem_size = + output_state_ref.element.current_size().to_f64().as_local(); + let elem_location = Point::::from(( + output_geometry.size.w as f64 / 2. - elem_size.w / 2., + output_geometry.size.h as f64 / 4. * 3. - elem_size.h / 2., + )); + let position = Point::<_, Local>::from(( + location.x, + elem_location.y + elem_size.h, + )); + + let grab = MenuGrab::new( + start_data, + &seat, + vec![ + Item::new("View moves with pointer", move |handle| { + let _ = handle.insert_idle(move |state| { + state + .common + .config + .cosmic_conf + .accessibility_zoom + .view_moves = ZoomMovement::Continuously; + state.common.update_config(); + // TODO: Write config + }); + }) + .toggled(movement == ZoomMovement::Continuously), + Item::new( + "View moves when pointer reaches edge", + move |handle| { + let _ = handle.insert_idle(move |state| { + state + .common + .config + .cosmic_conf + .accessibility_zoom + .view_moves = ZoomMovement::OnEdge; + state.common.update_config(); + // TODO: Write config + }); + }, + ) + .toggled(movement == ZoomMovement::OnEdge), + Item::new( + "View moves to keep pointer centered", + move |handle| { + let _ = handle.insert_idle(move |state| { + state + .common + .config + .cosmic_conf + .accessibility_zoom + .view_moves = ZoomMovement::Centered; + state.common.update_config(); + // TODO: Write config + }); + }, + ) + .toggled(movement == ZoomMovement::Centered), + Item::new("Magnifier settings...", |_| { /* TODO */ }), + ] + .into_iter(), + position.to_global(&output).to_i32_round(), + MenuAlignment::HORIZONTALLY_CENTERED, + true, + state.common.event_loop_handle.clone(), + state.common.theme.clone(), + ); + + std::mem::drop(shell); + if grab.is_touch_grab() { + seat.get_touch().unwrap().set_grab(state, grab, serial); + } else { + seat.get_pointer().unwrap().set_grab( + state, + grab, + serial, + Focus::Clear, + ); + } + } + } + }); + } + } + ZoomMessage::Increment => { + if let Some((seat, serial)) = last_seat.cloned() { + let increments = self.increments.clone(); + let _ = loop_handle.insert_idle(move |state| { + if let Some(start_data) = + check_grab_preconditions(&seat, Some(serial), None) + { + let shell = state.common.shell.read().unwrap(); + let output = seat.active_output(); + + if let Some(zoom_state) = shell.zoom_state() { + let location = global_pos_to_screen_space( + start_data.location().as_global(), + &output, + zoom_state.current_level(), + ); + + let output_geometry = output.geometry(); + let output_state = + output.user_data().get::>().unwrap(); + let output_state_ref = output_state.lock().unwrap(); + + let elem_size = + output_state_ref.element.current_size().to_f64().as_local(); + let elem_location = Point::::from(( + output_geometry.size.w as f64 / 2. - elem_size.w / 2., + output_geometry.size.h as f64 / 4. * 3. - elem_size.h / 2., + )); + let position = Point::<_, Local>::from(( + location.x, + elem_location.y + elem_size.h, + )); + + let grab = MenuGrab::new( + start_data, + &seat, + increments.into_iter().map(|val| { + Item::new(format!("{}%", val), move |handle| { + let _ = handle.insert_idle(move |state| { + state + .common + .config + .cosmic_conf + .accessibility_zoom + .increment = val; + state.common.update_config(); + // TODO: Write config + }); + }) + }), + position.to_global(&output).to_i32_round(), + MenuAlignment::CENTERED, + true, + state.common.event_loop_handle.clone(), + state.common.theme.clone(), + ); + + std::mem::drop(shell); + if grab.is_touch_grab() { + seat.get_touch().unwrap().set_grab(state, grab, serial); + } else { + seat.get_pointer().unwrap().set_grab( + state, + grab, + serial, + Focus::Clear, + ); + } + } + } + }); + } + + /* + let new_increment = self.increments[idx]; + let _ = loop_handle.insert_idle(move |state| { + state.common.config.cosmic_conf.accessibility_zoom.increment = new_increment; + // TODO: Write config + }); + */ + } + ZoomMessage::Close => { + let _ = loop_handle.insert_idle(|state| { + let seat = state + .common + .shell + .read() + .unwrap() + .seats + .last_active() + .clone(); + state.common.shell.write().unwrap().trigger_zoom( + &seat, + 1.0, + &state.common.config.cosmic_conf.accessibility_zoom, + true, + &state.common.event_loop_handle, + ); + }); + } + ZoomMessage::Update { + level, + increment, + movement, + } => { + self.level = level; + self.movement = movement; + + if let Some(pos) = self.increments.iter().position(|val| *val == increment) { + self.increment_idx = pos; + } else { + let mut increments = vec![25, 50, 100, 150, 200]; + if !increments.contains(&increment) { + increments.push(increment); + } + increments.sort(); + self.increment_idx = + increments.iter().position(|val| *val == increment).unwrap(); + self.increments = increments; + } + } + } + cosmic::Task::none() + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ZoomFocusTarget { + Main(ZoomElement), + Menu(IcedElement), +} + +impl From for ZoomFocusTarget { + fn from(value: ZoomElement) -> Self { + ZoomFocusTarget::Main(value) + } +} + +impl From> for ZoomFocusTarget { + fn from(value: IcedElement) -> Self { + ZoomFocusTarget::Menu(value) + } +} + +impl PointerTarget for ZoomFocusTarget { + fn enter(&self, seat: &Seat, data: &mut State, event: &PointerMotionEvent) { + match self { + ZoomFocusTarget::Main(elem) => PointerTarget::enter(elem, seat, data, event), + ZoomFocusTarget::Menu(elem) => PointerTarget::enter(elem, seat, data, event), + } + } + + fn motion(&self, seat: &Seat, data: &mut State, event: &PointerMotionEvent) { + match self { + ZoomFocusTarget::Main(elem) => PointerTarget::motion(elem, seat, data, event), + ZoomFocusTarget::Menu(elem) => PointerTarget::motion(elem, seat, data, event), + } + } + + fn relative_motion(&self, seat: &Seat, data: &mut State, event: &RelativeMotionEvent) { + match self { + ZoomFocusTarget::Main(elem) => PointerTarget::relative_motion(elem, seat, data, event), + ZoomFocusTarget::Menu(elem) => PointerTarget::relative_motion(elem, seat, data, event), + } + } + + fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { + match self { + ZoomFocusTarget::Main(elem) => PointerTarget::button(elem, seat, data, event), + ZoomFocusTarget::Menu(elem) => PointerTarget::button(elem, seat, data, event), + } + } + + fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { + match self { + ZoomFocusTarget::Main(elem) => PointerTarget::axis(elem, seat, data, frame), + ZoomFocusTarget::Menu(elem) => PointerTarget::axis(elem, seat, data, frame), + } + } + + fn frame(&self, seat: &Seat, data: &mut State) { + match self { + ZoomFocusTarget::Main(elem) => PointerTarget::frame(elem, seat, data), + ZoomFocusTarget::Menu(elem) => PointerTarget::frame(elem, seat, data), + } + } + + fn gesture_swipe_begin( + &self, + seat: &Seat, + data: &mut State, + event: &GestureSwipeBeginEvent, + ) { + match self { + ZoomFocusTarget::Main(elem) => { + PointerTarget::gesture_swipe_begin(elem, seat, data, event) + } + ZoomFocusTarget::Menu(elem) => { + PointerTarget::gesture_swipe_begin(elem, seat, data, event) + } + } + } + + fn gesture_swipe_update( + &self, + seat: &Seat, + data: &mut State, + event: &GestureSwipeUpdateEvent, + ) { + match self { + ZoomFocusTarget::Main(elem) => { + PointerTarget::gesture_swipe_update(elem, seat, data, event) + } + ZoomFocusTarget::Menu(elem) => { + PointerTarget::gesture_swipe_update(elem, seat, data, event) + } + } + } + + fn gesture_swipe_end( + &self, + seat: &Seat, + data: &mut State, + event: &GestureSwipeEndEvent, + ) { + match self { + ZoomFocusTarget::Main(elem) => { + PointerTarget::gesture_swipe_end(elem, seat, data, event) + } + ZoomFocusTarget::Menu(elem) => { + PointerTarget::gesture_swipe_end(elem, seat, data, event) + } + } + } + + fn gesture_pinch_begin( + &self, + seat: &Seat, + data: &mut State, + event: &GesturePinchBeginEvent, + ) { + match self { + ZoomFocusTarget::Main(elem) => { + PointerTarget::gesture_pinch_begin(elem, seat, data, event) + } + ZoomFocusTarget::Menu(elem) => { + PointerTarget::gesture_pinch_begin(elem, seat, data, event) + } + } + } + + fn gesture_pinch_update( + &self, + seat: &Seat, + data: &mut State, + event: &GesturePinchUpdateEvent, + ) { + match self { + ZoomFocusTarget::Main(elem) => { + PointerTarget::gesture_pinch_update(elem, seat, data, event) + } + ZoomFocusTarget::Menu(elem) => { + PointerTarget::gesture_pinch_update(elem, seat, data, event) + } + } + } + + fn gesture_pinch_end( + &self, + seat: &Seat, + data: &mut State, + event: &GesturePinchEndEvent, + ) { + match self { + ZoomFocusTarget::Main(elem) => { + PointerTarget::gesture_pinch_end(elem, seat, data, event) + } + ZoomFocusTarget::Menu(elem) => { + PointerTarget::gesture_pinch_end(elem, seat, data, event) + } + } + } + + fn gesture_hold_begin( + &self, + seat: &Seat, + data: &mut State, + event: &GestureHoldBeginEvent, + ) { + match self { + ZoomFocusTarget::Main(elem) => { + PointerTarget::gesture_hold_begin(elem, seat, data, event) + } + ZoomFocusTarget::Menu(elem) => { + PointerTarget::gesture_hold_begin(elem, seat, data, event) + } + } + } + + fn gesture_hold_end(&self, seat: &Seat, data: &mut State, event: &GestureHoldEndEvent) { + match self { + ZoomFocusTarget::Main(elem) => PointerTarget::gesture_hold_end(elem, seat, data, event), + ZoomFocusTarget::Menu(elem) => PointerTarget::gesture_hold_end(elem, seat, data, event), + } + } + + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { + match self { + ZoomFocusTarget::Main(elem) => PointerTarget::leave(elem, seat, data, serial, time), + ZoomFocusTarget::Menu(elem) => PointerTarget::leave(elem, seat, data, serial, time), + } + } +} + +impl TouchTarget for ZoomFocusTarget { + fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent, seq: Serial) { + match self { + ZoomFocusTarget::Main(elem) => TouchTarget::down(elem, seat, data, event, seq), + ZoomFocusTarget::Menu(elem) => TouchTarget::down(elem, seat, data, event, seq), + } + } + + fn up(&self, seat: &Seat, data: &mut State, event: &UpEvent, seq: Serial) { + match self { + ZoomFocusTarget::Main(elem) => TouchTarget::up(elem, seat, data, event, seq), + ZoomFocusTarget::Menu(elem) => TouchTarget::up(elem, seat, data, event, seq), + } + } + + fn motion(&self, seat: &Seat, data: &mut State, event: &TouchMotionEvent, seq: Serial) { + match self { + ZoomFocusTarget::Main(elem) => TouchTarget::motion(elem, seat, data, event, seq), + ZoomFocusTarget::Menu(elem) => TouchTarget::motion(elem, seat, data, event, seq), + } + } + + fn frame(&self, seat: &Seat, data: &mut State, seq: Serial) { + match self { + ZoomFocusTarget::Main(elem) => TouchTarget::frame(elem, seat, data, seq), + ZoomFocusTarget::Menu(elem) => TouchTarget::frame(elem, seat, data, seq), + } + } + + fn cancel(&self, seat: &Seat, data: &mut State, seq: Serial) { + match self { + ZoomFocusTarget::Main(elem) => TouchTarget::cancel(elem, seat, data, seq), + ZoomFocusTarget::Menu(elem) => TouchTarget::cancel(elem, seat, data, seq), + } + } + + fn shape(&self, seat: &Seat, data: &mut State, event: &ShapeEvent, seq: Serial) { + match self { + ZoomFocusTarget::Main(elem) => TouchTarget::shape(elem, seat, data, event, seq), + ZoomFocusTarget::Menu(elem) => TouchTarget::shape(elem, seat, data, event, seq), + } + } + + fn orientation( + &self, + seat: &Seat, + data: &mut State, + event: &OrientationEvent, + seq: Serial, + ) { + match self { + ZoomFocusTarget::Main(elem) => TouchTarget::orientation(elem, seat, data, event, seq), + ZoomFocusTarget::Menu(elem) => TouchTarget::orientation(elem, seat, data, event, seq), + } + } +} + +impl IsAlive for ZoomFocusTarget { + fn alive(&self) -> bool { + match self { + ZoomFocusTarget::Main(elem) => elem.alive(), + ZoomFocusTarget::Menu(elem) => elem.alive(), + } + } +} diff --git a/src/utils/geometry.rs b/src/utils/geometry.rs index e6b4055a..8b241514 100644 --- a/src/utils/geometry.rs +++ b/src/utils/geometry.rs @@ -20,6 +20,7 @@ pub trait PointExt { pub trait PointGlobalExt { fn to_local(self, output: &Output) -> Point; + fn to_zoomed(self, output: &Output, level: f64) -> Point; fn as_logical(self) -> Point; } @@ -65,6 +66,14 @@ impl PointGlobalExt for Point { (C::from_f64(point.x), C::from_f64(point.y)).into() } + fn to_zoomed(self, output: &Output, level: f64) -> Point { + let zoomed_output_geometry = output.zoomed_geometry(level).unwrap(); + let point = (self.to_f64() - zoomed_output_geometry.loc.to_f64()) + .upscale(level) + .as_logical(); + (C::from_f64(point.x), C::from_f64(point.y)).into() + } + fn as_logical(self) -> Point { (self.x, self.y).into() } diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 4e4a93d7..056ebd4c 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -5,10 +5,13 @@ use smithay::{ }; pub use super::geometry::*; -use crate::config::{AdaptiveSync, OutputConfig, OutputState}; pub use crate::shell::{SeatExt, Shell, Workspace}; pub use crate::state::{Common, State}; pub use crate::wayland::handlers::xdg_shell::popup::update_reactive_popups; +use crate::{ + config::{AdaptiveSync, OutputConfig, OutputState}, + shell::zoom::OutputZoomState, +}; use std::{ cell::{Ref, RefCell, RefMut}, @@ -20,6 +23,8 @@ use std::{ pub trait OutputExt { fn geometry(&self) -> Rectangle; + fn zoomed_geometry(&self, level: f64) -> Option>; + fn adaptive_sync(&self) -> AdaptiveSync; fn set_adaptive_sync(&self, vrr: AdaptiveSync); fn adaptive_sync_support(&self) -> Option; @@ -52,6 +57,21 @@ impl OutputExt for Output { .as_global() } + fn zoomed_geometry(&self, level: f64) -> Option> { + let output_geometry = self.geometry(); + + let output_state = self.user_data().get::>()?; + let mut output_state_ref = output_state.lock().unwrap(); + + let focal_point = output_state_ref.focal_point().to_global(self); + let mut zoomed_output_geo = output_geometry.to_f64(); + zoomed_output_geo.loc -= focal_point; + zoomed_output_geo = zoomed_output_geo.downscale(level); + zoomed_output_geo.loc += focal_point; + + Some(zoomed_output_geo.to_i32_round()) + } + fn adaptive_sync(&self) -> AdaptiveSync { self.user_data() .get::()