a11y/zoom: zoom level per output

This commit is contained in:
Victoria Brekenfeld 2025-03-25 17:31:48 +01:00 committed by Victoria Brekenfeld
parent babb96ddfa
commit 0ba0a0cdaa
11 changed files with 154 additions and 117 deletions

View file

@ -79,7 +79,14 @@ pub fn init_backend_auto(
.accessibility_zoom .accessibility_zoom
.start_on_login .start_on_login
{ {
state.update_zoom(&initial_seat, 1.0, true); state.common.shell.write().unwrap().trigger_zoom(
&initial_seat,
None,
1.0 + (state.common.config.cosmic_conf.accessibility_zoom.increment as f64 / 100.),
&state.common.config.cosmic_conf.accessibility_zoom,
true,
&state.common.event_loop_handle,
);
} }
let desired_numlock = state let desired_numlock = state

View file

@ -433,7 +433,7 @@ where
.map(|state| { .map(|state| {
( (
state.animating_focal_point(Some(&output)).to_local(&output), state.animating_focal_point(Some(&output)).to_local(&output),
state.animating_level(), state.animating_level(&output),
) )
}) })
.unwrap_or_else(|| ((0., 0.).into(), 1.)); .unwrap_or_else(|| ((0., 0.).into(), 1.));
@ -741,7 +741,7 @@ where
.map(|state| { .map(|state| {
( (
state.animating_focal_point(Some(&output)).to_local(&output), state.animating_focal_point(Some(&output)).to_local(&output),
state.animating_level(), state.animating_level(&output),
) )
}) })
.unwrap_or_else(|| ((0., 0.).into(), 1.)); .unwrap_or_else(|| ((0., 0.).into(), 1.));

View file

@ -1027,10 +1027,11 @@ impl State {
} }
pub fn update_zoom(&mut self, seat: &Seat<State>, change: f64, animate: bool) { pub fn update_zoom(&mut self, seat: &Seat<State>, change: f64, animate: bool) {
let output = seat.active_output();
let mut shell = self.common.shell.write().unwrap(); let mut shell = self.common.shell.write().unwrap();
let (zoom_seat, current_level) = shell let (zoom_seat, current_level) = shell
.zoom_state() .zoom_state()
.map(|state| (state.current_seat(), state.current_level())) .map(|state| (state.current_seat(), state.current_level(&output)))
.unwrap_or_else(|| (seat.clone(), 1.0)); .unwrap_or_else(|| (seat.clone(), 1.0));
if current_level == 1. && change <= 0. { if current_level == 1. && change <= 0. {
@ -1041,6 +1042,7 @@ impl State {
let new_level = (current_level + change).max(1.0); let new_level = (current_level + change).max(1.0);
shell.trigger_zoom( shell.trigger_zoom(
&seat, &seat,
Some(&output),
new_level, new_level,
&self.common.config.cosmic_conf.accessibility_zoom, &self.common.config.cosmic_conf.accessibility_zoom,
animate, animate,

View file

@ -2247,7 +2247,7 @@ where
B::Device: 'static, B::Device: 'static,
{ {
let geometry = zoom_state let geometry = zoom_state
.and_then(|state| output.zoomed_geometry(state.current_level())) .and_then(|_| output.zoomed_geometry())
.unwrap_or_else(|| output.geometry()); .unwrap_or_else(|| output.geometry());
let transform = output.current_transform(); let transform = output.current_transform();
let size = transform let size = transform

View file

@ -73,7 +73,7 @@ fn render_input_order_internal<R: 'static>(
if shell if shell
.zoom_state .zoom_state
.as_ref() .as_ref()
.is_some_and(|state| state.show_overlay && state.current_level() != 1.0) .is_some_and(|state| state.show_overlay && state.current_level(output) != 1.0)
{ {
callback(Stage::ZoomUI)?; callback(Stage::ZoomUI)?;
} }

View file

@ -522,11 +522,11 @@ impl PointerGrab<State> for MenuGrab {
let mut guard = self.elements.lock().unwrap(); let mut guard = self.elements.lock().unwrap();
let elements = &mut *guard; let elements = &mut *guard;
let event_location = if let Some(output) = self.screen_space_relative.as_ref() { let event_location = if let Some(output) = self.screen_space_relative.as_ref() {
if let Some(zoom_state) = state.common.shell.read().unwrap().zoom_state() { if state.common.shell.read().unwrap().zoom_state().is_some() {
event event
.location .location
.as_global() .as_global()
.to_zoomed(output, zoom_state.level) .to_zoomed(output)
.to_global(output) .to_global(output)
.as_logical() .as_logical()
} else { } else {
@ -726,11 +726,11 @@ impl TouchGrab<State> for MenuGrab {
let mut guard = self.elements.lock().unwrap(); let mut guard = self.elements.lock().unwrap();
let elements = &mut *guard; let elements = &mut *guard;
let event_location = if let Some(output) = self.screen_space_relative.as_ref() { let event_location = if let Some(output) = self.screen_space_relative.as_ref() {
if let Some(zoom_state) = data.common.shell.read().unwrap().zoom_state() { if data.common.shell.read().unwrap().zoom_state().is_some() {
event event
.location .location
.as_global() .as_global()
.to_zoomed(output, zoom_state.level) .to_zoomed(output)
.to_global(output) .to_global(output)
.as_logical() .as_logical()
} else { } else {

View file

@ -1202,7 +1202,7 @@ impl Common {
Mutex::new(OutputZoomState::new( Mutex::new(OutputZoomState::new(
&state.seat, &state.seat,
output, output,
state.level, 1.0,
state.increment, state.increment,
state.movement, state.movement,
self.event_loop_handle.clone(), self.event_loop_handle.clone(),
@ -1253,11 +1253,9 @@ impl Common {
for output in shell_ref.workspaces.sets.keys() { for output in shell_ref.workspaces.sets.keys() {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap(); let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
output_state.lock().unwrap().update( let mut output_state_ref = output_state.lock().unwrap();
zoom_state.level, let level = output_state_ref.level;
zoom_state.movement, output_state_ref.update(level, false, zoom_state.movement, zoom_state.increment);
zoom_state.increment,
);
} }
} }
@ -1861,13 +1859,12 @@ impl Shell {
.workspaces .workspaces
.spaces() .spaces()
.any(|workspace| workspace.animations_going()) .any(|workspace| workspace.animations_going())
|| self.zoom_state.as_ref().is_some_and(|state| { || self.zoom_state.as_ref().is_some_and(|_| {
state.previous_level.is_some() self.outputs().any(|o| {
|| self.outputs().any(|o| { o.user_data()
o.user_data() .get::<Mutex<OutputZoomState>>()
.get::<Mutex<OutputZoomState>>() .is_some_and(|state| state.lock().unwrap().is_animating())
.is_some_and(|state| state.lock().unwrap().is_animating()) })
})
}) })
} }
@ -2005,6 +2002,7 @@ impl Shell {
pub fn trigger_zoom( pub fn trigger_zoom(
&mut self, &mut self,
seat: &Seat<State>, seat: &Seat<State>,
output: Option<&Output>,
level: f64, level: f64,
zoom_config: &ZoomConfig, zoom_config: &ZoomConfig,
animate: bool, animate: bool,
@ -2014,51 +2012,53 @@ impl Shell {
return; return;
} }
let previous_level = if let Some(old_state) = self.zoom_state.as_ref() { let outputs = output.map(|o| vec![o]).unwrap_or(self.outputs().collect());
if &old_state.seat != seat { if self.zoom_state.is_none() {
return;
}
for output in self.outputs() { for output in self.outputs() {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap(); output.user_data().insert_if_missing_threadsafe(|| {
output_state.lock().unwrap().update(
level,
zoom_config.view_moves,
zoom_config.increment,
);
}
old_state.animating_level()
} else {
for output in self.outputs() {
let output_state = output.user_data().get_or_insert_threadsafe(|| {
Mutex::new(OutputZoomState::new( Mutex::new(OutputZoomState::new(
seat, seat,
output, output,
level, 1.0,
zoom_config.increment, zoom_config.increment,
zoom_config.view_moves, zoom_config.view_moves,
loop_handle.clone(), loop_handle.clone(),
self.theme.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. let mut toggled = self.zoom_state.is_none();
}; if let Some(old_state) = self.zoom_state.as_ref() {
if &old_state.seat != seat {
return;
}
}
for output in &outputs {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
output_state.lock().unwrap().update(
level,
animate,
zoom_config.view_moves,
zoom_config.increment,
);
}
let all_outputs_off = self.outputs().all(|o| {
o.user_data()
.get::<Mutex<OutputZoomState>>()
.unwrap()
.lock()
.unwrap()
.current_level()
== 1.0
});
toggled = toggled || all_outputs_off;
let toggled = previous_level != level && (previous_level == 1.0 || level == 1.0);
if toggled { if toggled {
let value = previous_level == 1.0; let value = !all_outputs_off;
let _ = loop_handle.insert_idle(move |state| { let _ = loop_handle.insert_idle(move |state| {
state.common.a11y_state.set_screen_magnifier(value); state.common.a11y_state.set_screen_magnifier(value);
}); });
@ -2067,10 +2067,8 @@ impl Shell {
self.zoom_state = Some(ZoomState { self.zoom_state = Some(ZoomState {
seat: seat.clone(), seat: seat.clone(),
show_overlay: zoom_config.show_overlay, show_overlay: zoom_config.show_overlay,
level,
increment: zoom_config.increment, increment: zoom_config.increment,
movement: zoom_config.view_moves, movement: zoom_config.view_moves,
previous_level: animate.then_some((previous_level, Instant::now())),
}); });
} }
@ -2135,31 +2133,21 @@ impl Shell {
_ => {} _ => {}
} }
if let Some(zoom_state) = self.zoom_state.as_mut() { if self.zoom_state.is_some() {
if zoom_state let mut all_outputs_off = true;
.previous_level for output in self.outputs() {
.as_ref() all_outputs_off = all_outputs_off
.is_some_and(|(_, start)| { && output
Instant::now().duration_since(*start) > ANIMATION_DURATION
})
{
zoom_state.previous_level.take();
}
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() .user_data()
.get::<Mutex<OutputZoomState>>() .get::<Mutex<OutputZoomState>>()
.unwrap() .unwrap()
.lock() .lock()
.unwrap() .unwrap()
.refresh(); .refresh();
} }
if all_outputs_off {
self.zoom_state.take();
} }
} }

View file

@ -52,14 +52,14 @@ use super::{
pub struct ZoomState { pub struct ZoomState {
pub(super) seat: Seat<State>, pub(super) seat: Seat<State>,
pub(super) show_overlay: bool, pub(super) show_overlay: bool,
pub(super) level: f64,
pub(super) increment: u32, pub(super) increment: u32,
pub(super) movement: ZoomMovement, pub(super) movement: ZoomMovement,
pub(super) previous_level: Option<(f64, Instant)>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct OutputZoomState { pub struct OutputZoomState {
pub(super) level: f64,
pub(super) previous_level: Option<(f64, Instant)>,
focal_point: Point<f64, Local>, focal_point: Point<f64, Local>,
previous_point: Option<(Point<f64, Local>, Instant)>, previous_point: Option<(Point<f64, Local>, Instant)>,
element: ZoomElement, element: ZoomElement,
@ -118,6 +118,8 @@ impl OutputZoomState {
element.set_additional_scale(level.min(4.)); element.set_additional_scale(level.min(4.));
OutputZoomState { OutputZoomState {
level,
previous_level: None,
focal_point, focal_point,
previous_point: None, previous_point: None,
element, element,
@ -150,15 +152,40 @@ impl OutputZoomState {
self.focal_point self.focal_point
} }
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 is_animating(&self) -> bool { pub fn is_animating(&self) -> bool {
self.previous_point.is_some() self.previous_point.is_some() || self.previous_level.is_some()
} }
pub fn refresh(&self) { pub fn refresh(&mut self) -> bool {
self.element.refresh() 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()
} }
pub fn update(&self, level: f64, movement: ZoomMovement, increment: u32) { 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.)); self.element.set_additional_scale(level.min(4.));
self.element.queue_message(ZoomMessage::Update { self.element.queue_message(ZoomMessage::Update {
level, level,
@ -189,25 +216,20 @@ impl OutputZoomState {
} }
impl ZoomState { 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<State> { pub fn current_seat(&self) -> Seat<State> {
self.seat.clone() self.seat.clone()
} }
pub fn current_level(&self, output: &Output) -> f64 {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
output_state.lock().unwrap().current_level()
}
pub fn animating_level(&self, output: &Output) -> f64 {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
output_state.lock().unwrap().animating_level()
}
pub fn animating_focal_point(&self, output: Option<&Output>) -> Point<f64, Global> { pub fn animating_focal_point(&self, output: Option<&Output>) -> Point<f64, Global> {
let active_output = self.seat.active_output(); let active_output = self.seat.active_output();
let output = output.unwrap_or(&active_output); let output = output.unwrap_or(&active_output);
@ -242,7 +264,7 @@ impl ZoomState {
let cursor_position = cursor_position.to_i32_round(); let cursor_position = cursor_position.to_i32_round();
let original_position = original_position.to_i32_round(); let original_position = original_position.to_i32_round();
let output_geometry = output.geometry(); let output_geometry = output.geometry();
let mut zoomed_output_geometry = output.zoomed_geometry(self.level).unwrap(); let mut zoomed_output_geometry = output.zoomed_geometry().unwrap();
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap(); let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
let mut output_state_ref = output_state.lock().unwrap(); let mut output_state_ref = output_state.lock().unwrap();
@ -285,7 +307,7 @@ impl ZoomState {
let mut diff = output_state_ref.focal_point.to_global(&output) let mut diff = output_state_ref.focal_point.to_global(&output)
+ (cursor_position.to_global(&output) - original_position) + (cursor_position.to_global(&output) - original_position)
.to_f64() .to_f64()
.upscale(self.level); .upscale(output_state_ref.level);
diff.x = diff.x.clamp( diff.x = diff.x.clamp(
output_geometry.loc.x as f64, 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 ((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
@ -333,9 +355,9 @@ impl ZoomState {
pos: Point<f64, Global>, pos: Point<f64, Global>,
) -> Option<(PointerFocusTarget, Point<f64, Global>)> { ) -> Option<(PointerFocusTarget, Point<f64, Global>)> {
let output_geometry = output.geometry(); let output_geometry = output.geometry();
let zoomed_output_geometry = output.zoomed_geometry(self.level).unwrap().to_f64(); let zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64();
let local_pos = global_pos_to_screen_space(pos, output);
let local_pos = global_pos_to_screen_space(pos, output, self.level);
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap(); let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
let output_state_ref = output_state.lock().unwrap(); let output_state_ref = output_state.lock().unwrap();
@ -351,7 +373,7 @@ impl ZoomState {
PointerFocusTarget::ZoomUI(output_state_ref.element.clone().into()), PointerFocusTarget::ZoomUI(output_state_ref.element.clone().into()),
{ {
// and vise-versa from screen-space to zoom-space... // and vise-versa from screen-space to zoom-space...
let scaled_loc = location.downscale(self.level); let scaled_loc = location.downscale(output_state_ref.level);
let global_loc = Point::<f64, Global>::from((scaled_loc.x, scaled_loc.y)) let global_loc = Point::<f64, Global>::from((scaled_loc.x, scaled_loc.y))
+ zoomed_output_geometry.loc; + zoomed_output_geometry.loc;
@ -359,7 +381,7 @@ impl ZoomState {
// the relative position for us... Which will be wrong given the cursor movement will // 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. // 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... // So we shift the location relatively to make up for the scaled movement...
let diff = (pos - global_loc).upscale(self.level - 1.); let diff = (pos - global_loc).upscale(output_state_ref.level - 1.);
global_loc - diff global_loc - diff
}, },
@ -383,10 +405,16 @@ impl ZoomState {
fn global_pos_to_screen_space( fn global_pos_to_screen_space(
pos: impl Into<Point<f64, Global>>, pos: impl Into<Point<f64, Global>>,
output: &Output, output: &Output,
level: f64,
) -> Point<f64, Local> { ) -> Point<f64, Local> {
let pos = pos.into(); let pos = pos.into();
let zoomed_output_geometry = output.zoomed_geometry(level).unwrap().to_f64(); let zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64();
let level = output
.user_data()
.get::<Mutex<OutputZoomState>>()
.unwrap()
.lock()
.unwrap()
.current_level();
// lets try to get the global cursor position into screen space // lets try to get the global cursor position into screen space
let relative_to_zoom_geo = Point::<f64, Local>::from(( let relative_to_zoom_geo = Point::<f64, Local>::from((
@ -551,11 +579,10 @@ impl Program for ZoomProgram {
let shell = state.common.shell.read().unwrap(); let shell = state.common.shell.read().unwrap();
let output = seat.active_output(); let output = seat.active_output();
if let Some(zoom_state) = shell.zoom_state() { if shell.zoom_state().is_some() {
let location = global_pos_to_screen_space( let location = global_pos_to_screen_space(
start_data.location().as_global(), start_data.location().as_global(),
&output, &output,
zoom_state.current_level(),
); );
let output_geometry = output.geometry(); let output_geometry = output.geometry();
@ -573,6 +600,7 @@ impl Program for ZoomProgram {
location.x, location.x,
elem_location.y + elem_size.h / 2., elem_location.y + elem_size.h / 2.,
)); ));
let level = output_state_ref.level;
std::mem::drop(output_state_ref); std::mem::drop(output_state_ref);
let grab = MenuGrab::new( let grab = MenuGrab::new(
@ -684,7 +712,7 @@ impl Program for ZoomProgram {
(elem_size.h / 2.).round() as u32, (elem_size.h / 2.).round() as u32,
false, false,
), ),
Some(zoom_state.level.min(4.)), Some(level.min(4.)),
state.common.event_loop_handle.clone(), state.common.event_loop_handle.clone(),
state.common.theme.clone(), state.common.theme.clone(),
); );
@ -715,11 +743,10 @@ impl Program for ZoomProgram {
let shell = state.common.shell.read().unwrap(); let shell = state.common.shell.read().unwrap();
let output = seat.active_output(); let output = seat.active_output();
if let Some(zoom_state) = shell.zoom_state() { if shell.zoom_state().is_some() {
let location = global_pos_to_screen_space( let location = global_pos_to_screen_space(
start_data.location().as_global(), start_data.location().as_global(),
&output, &output,
zoom_state.current_level(),
); );
let output_geometry = output.geometry(); let output_geometry = output.geometry();
@ -737,6 +764,7 @@ impl Program for ZoomProgram {
location.x, location.x,
elem_location.y + (elem_size.h / 2.), elem_location.y + (elem_size.h / 2.),
)); ));
let level = output_state_ref.level;
std::mem::drop(output_state_ref); std::mem::drop(output_state_ref);
let grab = MenuGrab::new( let grab = MenuGrab::new(
@ -758,7 +786,7 @@ impl Program for ZoomProgram {
}), }),
position.to_global(&output).to_i32_round(), position.to_global(&output).to_i32_round(),
MenuAlignment::PREFER_CENTERED, MenuAlignment::PREFER_CENTERED,
Some(zoom_state.level.min(4.)), Some(level.min(4.)),
state.common.event_loop_handle.clone(), state.common.event_loop_handle.clone(),
state.common.theme.clone(), state.common.theme.clone(),
); );

View file

@ -1,8 +1,12 @@
use std::sync::Mutex;
use smithay::{ use smithay::{
output::Output, output::Output,
utils::{Coordinate, Logical, Point, Rectangle, Size}, utils::{Coordinate, Logical, Point, Rectangle, Size},
}; };
use crate::shell::zoom::OutputZoomState;
use super::prelude::OutputExt; use super::prelude::OutputExt;
/// Marker type for coordinates in global space /// Marker type for coordinates in global space
@ -20,7 +24,7 @@ pub trait PointExt<C: Coordinate> {
pub trait PointGlobalExt<C: Coordinate> { pub trait PointGlobalExt<C: Coordinate> {
fn to_local(self, output: &Output) -> Point<C, Local>; fn to_local(self, output: &Output) -> Point<C, Local>;
fn to_zoomed(self, output: &Output, level: f64) -> Point<C, Local>; fn to_zoomed(self, output: &Output) -> Point<C, Local>;
fn as_logical(self) -> Point<C, Logical>; fn as_logical(self) -> Point<C, Logical>;
} }
@ -66,8 +70,15 @@ impl<C: Coordinate> PointGlobalExt<C> for Point<C, Global> {
(C::from_f64(point.x), C::from_f64(point.y)).into() (C::from_f64(point.x), C::from_f64(point.y)).into()
} }
fn to_zoomed(self, output: &Output, level: f64) -> Point<C, Local> { fn to_zoomed(self, output: &Output) -> Point<C, Local> {
let zoomed_output_geometry = output.zoomed_geometry(level).unwrap(); let zoomed_output_geometry = output.zoomed_geometry().unwrap();
let level = output
.user_data()
.get::<Mutex<OutputZoomState>>()
.unwrap()
.lock()
.unwrap()
.current_level();
let point = (self.to_f64() - zoomed_output_geometry.loc.to_f64()) let point = (self.to_f64() - zoomed_output_geometry.loc.to_f64())
.upscale(level) .upscale(level)
.as_logical(); .as_logical();

View file

@ -23,7 +23,7 @@ use std::{
pub trait OutputExt { pub trait OutputExt {
fn geometry(&self) -> Rectangle<i32, Global>; fn geometry(&self) -> Rectangle<i32, Global>;
fn zoomed_geometry(&self, level: f64) -> Option<Rectangle<i32, Global>>; fn zoomed_geometry(&self) -> Option<Rectangle<i32, Global>>;
fn adaptive_sync(&self) -> AdaptiveSync; fn adaptive_sync(&self) -> AdaptiveSync;
fn set_adaptive_sync(&self, vrr: AdaptiveSync); fn set_adaptive_sync(&self, vrr: AdaptiveSync);
@ -57,7 +57,7 @@ impl OutputExt for Output {
.as_global() .as_global()
} }
fn zoomed_geometry(&self, level: f64) -> Option<Rectangle<i32, Global>> { fn zoomed_geometry(&self) -> Option<Rectangle<i32, Global>> {
let output_geometry = self.geometry(); let output_geometry = self.geometry();
let output_state = self.user_data().get::<Mutex<OutputZoomState>>()?; let output_state = self.user_data().get::<Mutex<OutputZoomState>>()?;
@ -66,7 +66,7 @@ impl OutputExt for Output {
let focal_point = output_state_ref.current_focal_point().to_global(self); let focal_point = output_state_ref.current_focal_point().to_global(self);
let mut zoomed_output_geo = output_geometry.to_f64(); let mut zoomed_output_geo = output_geometry.to_f64();
zoomed_output_geo.loc -= focal_point; zoomed_output_geo.loc -= focal_point;
zoomed_output_geo = zoomed_output_geo.downscale(level); zoomed_output_geo = zoomed_output_geo.downscale(output_state_ref.current_level());
zoomed_output_geo.loc += focal_point; zoomed_output_geo.loc += focal_point;
Some(zoomed_output_geo.to_i32_round()) Some(zoomed_output_geo.to_i32_round())

View file

@ -16,7 +16,7 @@ impl A11yHandler for State {
if shell if shell
.zoom_state() .zoom_state()
.is_some_and(|state| state.current_level() != 1.0) .is_some_and(|state| shell.outputs().any(|o| state.current_level(o) != 1.0))
!= enabled != enabled
{ {
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
@ -29,6 +29,7 @@ impl A11yHandler for State {
shell.trigger_zoom( shell.trigger_zoom(
&seat, &seat,
None,
level, level,
zoom_config, zoom_config,
true, true,