cosmic-comp/src/shell/zoom.rs

1098 lines
44 KiB
Rust
Raw Normal View History

2025-02-13 21:09:13 +01:00
use std::{sync::Mutex, time::Instant};
use calloop::LoopHandle;
use cosmic::{
2025-10-16 18:53:57 +02:00
Apply,
2026-04-17 11:48:11 +02:00
iced::{Alignment, Background, Border, Length, alignment::Vertical, widget as iced_widget},
theme,
2025-02-13 21:09:13 +01:00
widget::{self, icon::Named},
};
use cosmic_comp_config::ZoomMovement;
2025-02-14 19:14:18 +01:00
use cosmic_config::ConfigSet;
use keyframe::{ease, functions::Linear};
2025-02-13 21:09:13 +01:00
use smithay::{
backend::renderer::ImportMem,
2025-02-13 21:09:13 +01:00
desktop::space::SpaceElement,
input::{
2025-10-16 18:53:57 +02:00
Seat,
2025-02-13 21:09:13 +01:00
pointer::{
AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent,
},
touch::{
2026-06-29 17:20:02 +02:00
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
TouchTarget, UpEvent,
2025-02-13 21:09:13 +01:00
},
},
output::Output,
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
utils::{FrameExtents, IsAlive, Point, Rectangle, Serial, Size},
2025-02-13 21:09:13 +01:00
};
2025-02-14 19:14:18 +01:00
use tracing::error;
2025-02-13 21:09:13 +01:00
use crate::{
backend::render::element::AsGlowRenderer,
2025-02-13 21:09:13 +01:00
state::State,
utils::{
iced::{IcedElement, IcedRenderElement, Program},
2025-02-13 21:09:13 +01:00
prelude::*,
tween::EasePoint,
},
};
use super::{
2025-10-16 18:53:57 +02:00
ANIMATION_DURATION, check_grab_preconditions,
2025-02-13 21:09:13 +01:00
focus::target::PointerFocusTarget,
grabs::{ContextMenu, Item, MenuAlignment, MenuGrab},
};
#[derive(Debug, Clone)]
pub struct ZoomState {
pub(super) seat: Seat<State>,
2025-03-25 14:38:35 +01:00
pub(super) show_overlay: bool,
2025-02-13 21:09:13 +01:00
pub(super) increment: u32,
pub(super) movement: ZoomMovement,
}
#[derive(Debug)]
pub struct OutputZoomState {
2025-03-25 17:31:48 +01:00
pub(super) level: f64,
pub(super) previous_level: Option<(f64, Instant)>,
2025-02-13 21:09:13 +01:00
focal_point: Point<f64, Local>,
previous_point: Option<(Point<f64, Local>, Instant)>,
element: ZoomElement,
}
impl OutputZoomState {
pub fn new(
seat: &Seat<State>,
output: &Output,
level: f64,
increment: u32,
movement: ZoomMovement,
loop_handle: LoopHandle<'static, State>,
mut theme: cosmic::Theme,
2025-02-13 21:09:13 +01:00
) -> OutputZoomState {
theme.transparent = theme.cosmic().frosted_system_interface;
2025-02-13 21:09:13 +01:00
let cursor_position = seat.get_pointer().unwrap().current_location().as_global();
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
let focal_point = cursor_position.to_local(output);
2025-02-13 21:09:13 +01:00
let output_geometry = output.geometry().to_f64();
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.min(output_geometry.size.w.round() as i32);
2025-02-13 21:09:13 +01:00
element.set_activate(true);
element.resize(size);
element.output_enter(output, Rectangle::new(Point::from((0, 0)), size));
element.set_additional_scale(level.min(4.));
2025-02-13 21:09:13 +01:00
OutputZoomState {
2025-03-25 17:31:48 +01:00
level,
previous_level: None,
2025-02-13 21:09:13 +01:00
focal_point,
previous_point: None,
element,
}
}
pub fn animating_focal_point(&mut self) -> Point<f64, Local> {
2025-02-13 21:09:13 +01:00
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(
Linear,
2025-02-13 21:09:13 +01:00
EasePoint(*old_point),
EasePoint(self.focal_point),
percentage,
)
.0
} else {
self.focal_point
}
}
pub fn current_focal_point(&mut self) -> Point<f64, Local> {
self.focal_point
}
2025-03-25 17:31:48 +01:00
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(Linear, *old_level, self.level, percentage)
2025-03-25 17:31:48 +01:00
} else {
self.level
}
}
2025-02-13 21:09:13 +01:00
pub fn is_animating(&self) -> bool {
2025-03-25 17:31:48 +01:00
self.previous_point.is_some() || self.previous_level.is_some()
2025-02-13 21:09:13 +01:00
}
2025-03-25 17:31:48 +01:00
pub fn refresh(&mut self) -> bool {
if self
.previous_level
.as_ref()
.is_some_and(|(_, start)| Instant::now().duration_since(*start) > ANIMATION_DURATION)
{
self.previous_level.take();
}
self.element.refresh();
self.level == 1. && self.previous_level.is_none()
2025-02-13 21:09:13 +01:00
}
2025-03-25 17:31:48 +01:00
pub fn update(&mut self, level: f64, animate: bool, movement: ZoomMovement, increment: u32) {
self.previous_level = animate.then_some((self.animating_level(), Instant::now()));
self.level = level;
self.element.set_additional_scale(level.min(4.));
2025-02-13 21:09:13 +01:00
self.element.queue_message(ZoomMessage::Update {
level,
movement,
increment,
});
}
fn render<R>(
&mut self,
renderer: &mut R,
output: &Output,
push: &mut dyn FnMut(IcedRenderElement<R>),
) where
R: AsGlowRenderer + ImportMem,
2025-03-11 19:14:49 +01:00
R::TextureId: Send + Clone + 'static,
2025-02-13 21:09:13 +01:00
{
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.push_render_elements(
renderer,
location,
scale.fractional_scale().into(),
1.0,
self.element
.with_theme(|theme| theme.cosmic().radius_s())
.map(|x| x.round() as u8),
push,
None,
)
2025-02-13 21:09:13 +01:00
}
}
impl ZoomState {
2025-03-25 17:31:48 +01:00
pub fn current_seat(&self) -> Seat<State> {
self.seat.clone()
2025-02-13 21:09:13 +01:00
}
2025-03-25 17:31:48 +01:00
pub fn current_level(&self, output: &Output) -> f64 {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
output_state.lock().unwrap().current_level()
2025-02-13 21:09:13 +01:00
}
2025-03-25 17:31:48 +01:00
pub fn animating_level(&self, output: &Output) -> f64 {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
output_state.lock().unwrap().animating_level()
2025-02-13 21:09:13 +01:00
}
pub fn animating_focal_point(&self, output: Option<&Output>) -> Point<f64, Global> {
let active_output = self.seat.active_output();
let output = output.unwrap_or(&active_output);
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
2026-02-23 16:25:06 +01:00
output_state
.lock()
.unwrap()
.animating_focal_point()
2026-02-23 16:25:06 +01:00
.to_global(output)
}
pub fn current_focal_point(&self, output: Option<&Output>) -> Point<f64, Global> {
2025-02-13 21:09:13 +01:00
let active_output = self.seat.active_output();
let output = output.unwrap_or(&active_output);
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
2026-02-23 16:25:06 +01:00
output_state
.lock()
.unwrap()
.current_focal_point()
2026-02-23 16:25:06 +01:00
.to_global(output)
2025-02-13 21:09:13 +01:00
}
pub fn update_focal_point(
&mut self,
output: &Output,
cursor_position: Point<f64, Global>,
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
_original_position: Point<f64, Global>,
2025-02-13 21:09:13 +01:00
movement: ZoomMovement,
) {
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
let output_geometry = output.geometry().to_f64().to_local(output);
let zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64().to_local(output);
2025-02-13 21:09:13 +01:00
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
let mut output_state_ref = output_state.lock().unwrap();
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
let level = output_state_ref.current_level();
let is_level_change = output_state_ref
.previous_level
.is_some_and(|prev| prev.0 != level);
// animate level and movement type changes
if is_level_change || self.movement != movement {
2025-02-13 21:09:13 +01:00
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 {
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
ZoomMovement::Continuously => {
// Focal point is the cursor position
output_state_ref.focal_point = cursor_position;
}
2025-02-13 21:09:13 +01:00
ZoomMovement::OnEdge => {
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
if is_level_change {
output_state_ref.focal_point = cursor_position;
return;
2025-02-13 21:09:13 +01:00
}
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
// Compute small margin relative to zoomed output to keep cursor within
let margin_size = zoomed_output_geometry.size.h * 0.02;
let margins = FrameExtents::new(margin_size, margin_size, margin_size, margin_size);
let inner_rect = zoomed_output_geometry - margins;
if inner_rect.contains(cursor_position) {
// Do not move if cursor within margins
return;
}
// Compute dx and dy to move the zoomed output based on cursor distance outside margin(s)
let dx = if cursor_position.x < inner_rect.loc.x {
cursor_position.x - inner_rect.loc.x
} else if cursor_position.x > inner_rect.loc.x + inner_rect.size.w {
cursor_position.x - (inner_rect.loc.x + inner_rect.size.w)
} else {
0.0
};
let dy = if cursor_position.y < inner_rect.loc.y {
cursor_position.y - inner_rect.loc.y
} else if cursor_position.y > inner_rect.loc.y + inner_rect.size.h {
cursor_position.y - (inner_rect.loc.y + inner_rect.size.h)
} else {
0.0
};
let mut focal_point = output_state_ref.focal_point + Point::new(dx, dy);
// Clamp the final focal point to output geometry
2025-02-13 21:09:13 +01:00
focal_point.x = focal_point.x.clamp(
output_geometry.loc.x,
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
output_geometry.loc.x + output_geometry.size.w - 1.0,
2025-02-13 21:09:13 +01:00
);
focal_point.y = focal_point.y.clamp(
output_geometry.loc.y,
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
output_geometry.loc.y + output_geometry.size.h - 1.0,
2025-02-13 21:09:13 +01:00
);
fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only).
2026-07-07 19:45:41 -06:00
output_state_ref.focal_point = focal_point;
}
ZoomMovement::Centered => {
let center = (output_geometry.size / 2.).to_point();
if level <= 1.0 + f64::EPSILON {
// Without this break, focal point will jump to (0, 0) on zoom out
output_state_ref.focal_point = center;
return;
}
// Compute translation to keep cursor at center of screen
let mut tx = center.x - cursor_position.x * level;
let mut ty = center.y - cursor_position.y * level;
// Clamp translation to keep viewport within screen bounds
tx = tx.clamp(output_geometry.size.w * (1.0 - level), 0.0);
ty = ty.clamp(output_geometry.size.h * (1.0 - level), 0.0);
// Convert translation back to focal point: T = F * (1 - level)
output_state_ref.focal_point =
Point::from((tx / (1.0 - level), ty / (1.0 - level)));
2025-02-13 21:09:13 +01:00
}
}
}
pub fn surface_under(
&self,
output: &Output,
pos: Point<f64, Global>,
) -> Option<(PointerFocusTarget, Point<f64, Global>)> {
let output_geometry = output.geometry();
2025-03-25 17:31:48 +01:00
let zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64();
let local_pos = global_pos_to_screen_space(pos, output);
2025-02-13 21:09:13 +01:00
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
let output_state_ref = output_state.lock().unwrap();
let size = output_state_ref.element.current_size().to_f64().as_local();
let location = Point::<f64, Local>::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...
2025-03-25 17:31:48 +01:00
let scaled_loc = location.downscale(output_state_ref.level);
2025-02-13 21:09:13 +01:00
let global_loc = Point::<f64, Global>::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...
2025-03-25 17:31:48 +01:00
let diff = (pos - global_loc).upscale(output_state_ref.level - 1.);
2025-02-13 21:09:13 +01:00
global_loc - diff
},
));
}
None
}
pub fn render<R>(renderer: &mut R, output: &Output, push: &mut dyn FnMut(IcedRenderElement<R>))
where
R: AsGlowRenderer + ImportMem,
2025-03-11 19:14:49 +01:00
R::TextureId: Send + Clone + 'static,
2025-02-13 21:09:13 +01:00
{
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
output_state.lock().unwrap().render(renderer, output, push)
2025-02-13 21:09:13 +01:00
}
}
fn global_pos_to_screen_space(
pos: impl Into<Point<f64, Global>>,
output: &Output,
) -> Point<f64, Local> {
let pos = pos.into();
2025-03-25 17:31:48 +01:00
let zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64();
let level = output
.user_data()
.get::<Mutex<OutputZoomState>>()
.unwrap()
.lock()
.unwrap()
.current_level();
2025-02-13 21:09:13 +01:00
// lets try to get the global cursor position into screen space
let relative_to_zoom_geo = Point::<f64, Local>::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<ZoomProgram>;
pub struct ZoomProgram {
level: f64,
increments: Vec<u32>,
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(theme.transparent).component;
2025-02-13 21:09:13 +01:00
iced_widget::container::Style {
2026-02-24 15:18:57 -05:00
snap: true,
2025-02-13 21:09:13 +01:00
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<State>, Serial)>,
) -> cosmic::Task<Self::Message> {
match message {
ZoomMessage::Decrease => {
let _ = loop_handle.insert_idle(|state| {
let seat = state.common.shell.read().seats.last_active().clone();
2025-02-13 21:09:13 +01:00
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().seats.last_active().clone();
2025-02-13 21:09:13 +01:00
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();
2025-02-13 21:09:13 +01:00
let output = seat.active_output();
2025-03-25 17:31:48 +01:00
if shell.zoom_state().is_some() {
2025-02-13 21:09:13 +01:00
let location = global_pos_to_screen_space(
start_data.location().as_global(),
&output,
);
let output_geometry = output.geometry();
let output_state =
output.user_data().get::<Mutex<OutputZoomState>>().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::<f64, Local>::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 / 2.,
2025-02-13 21:09:13 +01:00
));
2025-03-25 17:31:48 +01:00
let level = output_state_ref.level;
std::mem::drop(output_state_ref);
2025-02-13 21:09:13 +01:00
let mut theme = state.common.theme.clone();
theme.transparent = theme.cosmic().frosted_system_interface;
2025-02-13 21:09:13 +01:00
let grab = MenuGrab::new(
start_data,
&seat,
vec![
2025-02-14 19:14:18 +01:00
Item::new(
crate::fl!("a11y-zoom-move-continuously"),
move |handle| {
let _ = handle.insert_idle(move |state| {
state
.common
.config
.cosmic_conf
.accessibility_zoom
.view_moves = ZoomMovement::Continuously;
if let Err(err) =
state.common.config.cosmic_helper.set(
"accessibility_zoom",
state
.common
.config
.cosmic_conf
.accessibility_zoom,
)
{
error!(
?err,
"Failed to update zoom config"
);
}
2025-02-17 17:58:18 +01:00
state.common.update_config();
2025-02-14 19:14:18 +01:00
});
},
)
2025-02-13 21:09:13 +01:00
.toggled(movement == ZoomMovement::Continuously),
Item::new(
2025-02-14 19:14:18 +01:00
crate::fl!("a11y-zoom-move-onedge"),
2025-02-13 21:09:13 +01:00
move |handle| {
let _ = handle.insert_idle(move |state| {
state
.common
.config
.cosmic_conf
.accessibility_zoom
.view_moves = ZoomMovement::OnEdge;
2025-02-14 19:14:18 +01:00
if let Err(err) =
state.common.config.cosmic_helper.set(
"accessibility_zoom",
state
.common
.config
.cosmic_conf
.accessibility_zoom,
)
{
error!(
?err,
"Failed to update zoom config"
);
}
2025-02-17 17:58:18 +01:00
state.common.update_config();
2025-02-13 21:09:13 +01:00
});
},
)
.toggled(movement == ZoomMovement::OnEdge),
Item::new(
2025-02-14 19:14:18 +01:00
crate::fl!("a11y-zoom-move-centered"),
2025-02-13 21:09:13 +01:00
move |handle| {
let _ = handle.insert_idle(move |state| {
state
.common
.config
.cosmic_conf
.accessibility_zoom
.view_moves = ZoomMovement::Centered;
2025-02-14 19:14:18 +01:00
if let Err(err) =
state.common.config.cosmic_helper.set(
"accessibility_zoom",
state
.common
.config
.cosmic_conf
.accessibility_zoom,
)
{
error!(
?err,
"Failed to update zoom config"
);
}
2025-02-17 17:58:18 +01:00
state.common.update_config();
2025-02-13 21:09:13 +01:00
});
},
)
.toggled(movement == ZoomMovement::Centered),
2025-02-17 18:04:34 +01:00
Item::Separator,
2025-02-14 19:14:18 +01:00
Item::new(crate::fl!("a11y-zoom-settings"), |handle| {
let _ = handle.insert_idle(move |state| {
state.spawn_command(
2025-03-25 18:32:01 +01:00
"cosmic-settings accessibility-magnifier"
.into(),
2025-02-14 19:14:18 +01:00
);
});
}),
2025-02-13 21:09:13 +01:00
]
.into_iter(),
position.to_global(&output).to_i32_round(),
MenuAlignment::horizontally_centered(
(elem_size.h / 2.).round() as u32,
false,
),
2025-03-25 17:31:48 +01:00
Some(level.min(4.)),
2025-02-13 21:09:13 +01:00
state.common.event_loop_handle.clone(),
theme,
2025-02-13 21:09:13 +01:00
);
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();
2025-02-13 21:09:13 +01:00
let output = seat.active_output();
2025-03-25 17:31:48 +01:00
if shell.zoom_state().is_some() {
2025-02-13 21:09:13 +01:00
let location = global_pos_to_screen_space(
start_data.location().as_global(),
&output,
);
let output_geometry = output.geometry();
let output_state =
output.user_data().get::<Mutex<OutputZoomState>>().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::<f64, Local>::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 / 2.),
2025-02-13 21:09:13 +01:00
));
2025-03-25 17:31:48 +01:00
let level = output_state_ref.level;
std::mem::drop(output_state_ref);
2025-02-13 21:09:13 +01:00
let mut theme = state.common.theme.clone();
theme.transparent = theme.cosmic().frosted_system_interface;
2025-02-13 21:09:13 +01:00
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();
if let Err(err) =
state.common.config.cosmic_helper.set(
"accessibility_zoom",
state
.common
.config
.cosmic_conf
.accessibility_zoom,
)
{
error!(?err, "Failed to update zoom config");
}
2025-02-13 21:09:13 +01:00
});
})
}),
position.to_global(&output).to_i32_round(),
MenuAlignment::PREFER_CENTERED,
2025-03-25 17:31:48 +01:00
Some(level.min(4.)),
2025-02-13 21:09:13 +01:00
state.common.event_loop_handle.clone(),
theme,
2025-02-13 21:09:13 +01:00
);
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::Close => {
let _ = loop_handle.insert_idle(|state| {
2025-03-25 14:38:35 +01:00
state
2025-02-13 21:09:13 +01:00
.common
2025-03-25 14:38:35 +01:00
.config
.cosmic_conf
.accessibility_zoom
.show_overlay = false;
if let Err(err) = state.common.config.cosmic_helper.set(
"accessibility_zoom",
state.common.config.cosmic_conf.accessibility_zoom,
) {
error!(?err, "Failed to update zoom config");
}
state.common.update_config();
2025-02-13 21:09:13 +01:00
});
}
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<ContextMenu>),
}
impl From<ZoomElement> for ZoomFocusTarget {
fn from(value: ZoomElement) -> Self {
ZoomFocusTarget::Main(value)
}
}
impl From<IcedElement<ContextMenu>> for ZoomFocusTarget {
fn from(value: IcedElement<ContextMenu>) -> Self {
ZoomFocusTarget::Menu(value)
}
}
impl PointerTarget<State> for ZoomFocusTarget {
fn enter(&self, seat: &Seat<State>, 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<State>, 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<State>, 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<State>, 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<State>, 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<State>, 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<State>,
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<State>,
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<State>,
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<State>,
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<State>,
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<State>,
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<State>,
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<State>, 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<State>, 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<State> for ZoomFocusTarget {
2026-06-29 17:20:02 +02:00
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
2025-02-13 21:09:13 +01:00
match self {
2026-06-29 17:20:02 +02:00
ZoomFocusTarget::Main(elem) => TouchTarget::down(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::down(elem, seat, data, event),
2025-02-13 21:09:13 +01:00
}
}
2026-06-29 17:20:02 +02:00
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
2025-02-13 21:09:13 +01:00
match self {
2026-06-29 17:20:02 +02:00
ZoomFocusTarget::Main(elem) => TouchTarget::up(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::up(elem, seat, data, event),
2025-02-13 21:09:13 +01:00
}
}
2026-06-29 17:20:02 +02:00
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &TouchMotionEvent) {
2025-02-13 21:09:13 +01:00
match self {
2026-06-29 17:20:02 +02:00
ZoomFocusTarget::Main(elem) => TouchTarget::motion(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::motion(elem, seat, data, event),
2025-02-13 21:09:13 +01:00
}
}
2026-06-29 17:20:02 +02:00
fn frame(&self, seat: &Seat<State>, data: &mut State, frame: FrameMarker) {
2025-02-13 21:09:13 +01:00
match self {
2026-06-29 17:20:02 +02:00
ZoomFocusTarget::Main(elem) => TouchTarget::frame(elem, seat, data, frame),
ZoomFocusTarget::Menu(elem) => TouchTarget::frame(elem, seat, data, frame),
2025-02-13 21:09:13 +01:00
}
}
2026-06-29 17:20:02 +02:00
fn cancel(&self, seat: &Seat<State>, data: &mut State, frame: FrameMarker) {
2025-02-13 21:09:13 +01:00
match self {
2026-06-29 17:20:02 +02:00
ZoomFocusTarget::Main(elem) => TouchTarget::cancel(elem, seat, data, frame),
ZoomFocusTarget::Menu(elem) => TouchTarget::cancel(elem, seat, data, frame),
2025-02-13 21:09:13 +01:00
}
}
2026-06-29 17:20:02 +02:00
fn shape(&self, seat: &Seat<State>, data: &mut State, event: &ShapeEvent) {
2025-02-13 21:09:13 +01:00
match self {
2026-06-29 17:20:02 +02:00
ZoomFocusTarget::Main(elem) => TouchTarget::shape(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::shape(elem, seat, data, event),
2025-02-13 21:09:13 +01:00
}
}
2026-06-29 17:20:02 +02:00
fn orientation(&self, seat: &Seat<State>, data: &mut State, event: &OrientationEvent) {
match self {
ZoomFocusTarget::Main(elem) => TouchTarget::orientation(elem, seat, data, event),
ZoomFocusTarget::Menu(elem) => TouchTarget::orientation(elem, seat, data, event),
}
}
fn last_frame(&self, seat: &Seat<State>, data: &mut State) -> Option<FrameMarker> {
2025-02-13 21:09:13 +01:00
match self {
2026-06-29 17:20:02 +02:00
ZoomFocusTarget::Main(elem) => TouchTarget::last_frame(elem, seat, data),
ZoomFocusTarget::Menu(elem) => TouchTarget::last_frame(elem, seat, data),
2025-02-13 21:09:13 +01:00
}
}
}
impl IsAlive for ZoomFocusTarget {
fn alive(&self) -> bool {
match self {
ZoomFocusTarget::Main(elem) => elem.alive(),
ZoomFocusTarget::Menu(elem) => elem.alive(),
}
}
}