zoom: Handle multiple outputs

This commit is contained in:
Victoria Brekenfeld 2025-01-24 18:07:33 +01:00 committed by Victoria Brekenfeld
parent 61d44b3a9d
commit 531a1c951f
4 changed files with 259 additions and 32 deletions

View file

@ -595,7 +595,7 @@ where
} else {
ElementFilter::All
};
let zoom_level = shell.read().unwrap().zoom_level();
let zoom_level = shell.read().unwrap().zoom_level(Some(&output));
#[allow(unused_mut)]
let workspace_elements = workspace_elements(
@ -1013,7 +1013,7 @@ where
} else {
ElementFilter::All
};
let zoom_level = shell.read().unwrap().zoom_level();
let zoom_level = shell.read().unwrap().zoom_level(Some(&output));
let result = render_workspace(
gpu,

View file

@ -1028,20 +1028,24 @@ impl State {
x @ Action::ZoomIn | x @ Action::ZoomOut => {
let mut shell = self.common.shell.write().unwrap();
let pointer_loc = seat.get_pointer().unwrap().current_location().as_global();
let (zoom_seat, _, current_level) = shell
.zoom_level()
.unwrap_or_else(|| (seat.clone(), pointer_loc, 1.0));
let (zoom_seat, current_level) = shell
.zoom_level(None)
.map(|(s, _, l)| (s, l))
.unwrap_or_else(|| (seat.clone(), 1.0));
if &zoom_seat == seat {
let increment =
self.common.config.cosmic_conf.accessibility_zoom.increment as f64 / 100.0;
shell.trigger_zoom(
seat,
pointer_loc,
match x {
Action::ZoomIn => current_level + 1.0,
Action::ZoomOut => (current_level - 1.0).max(1.0),
Action::ZoomIn => current_level + increment,
Action::ZoomOut => (current_level - increment).max(1.0),
_ => unreachable!(),
},
self.common.config.cosmic_conf.accessibility_zoom.view_moves,
);
// TODO: persist state, if enable_on_startup
}
}

View file

@ -518,6 +518,11 @@ impl State {
let mut shell = self.common.shell.write().unwrap();
shell.update_pointer_position(position.to_local(&output), &output);
shell.update_focal_point(
&seat,
original_position,
self.common.config.cosmic_conf.accessibility_zoom.view_moves,
);
if output != current_output {
for session in cursor_sessions_for_output(&*shell, &current_output) {

View file

@ -10,10 +10,13 @@ use std::{
};
use wayland_backend::server::ClientId;
use crate::wayland::{handlers::data_device, protocols::workspace::WorkspaceCapabilities};
use crate::{
utils::{float::NextDown, tween::EasePoint},
wayland::{handlers::data_device, protocols::workspace::WorkspaceCapabilities},
};
use cosmic_comp_config::{
workspace::{WorkspaceLayout, WorkspaceMode},
TileBehavior,
TileBehavior, ZoomMovement,
};
use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::TilingState;
use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection, ResizeDirection};
@ -245,25 +248,112 @@ pub struct PendingLayer {
pub output: Output,
}
pub struct ZoomState {
struct ZoomState {
seat: Seat<State>,
level: f64,
focal_point: Point<f64, Global>,
previous: Option<(f64, Instant)>,
movement: ZoomMovement,
previous_level: Option<(f64, Instant)>,
}
#[derive(Debug)]
struct OutputZoomState {
focal_point: Point<f64, Local>,
previous_point: Option<(Point<f64, Local>, Instant)>,
}
impl OutputZoomState {
pub fn new(
seat: &Seat<State>,
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<f64, Local> {
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) -> (Seat<State>, Point<f64, Global>, f64) {
if let Some((old, start)) = self.previous.as_ref() {
pub fn level(&self, output: Option<&Output>) -> (Seat<State>, Point<f64, Global>, 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(),
self.focal_point,
ease(EaseInOutCubic, *old, self.level, percentage),
focal_point,
ease(EaseInOutCubic, *old_level, self.level, percentage),
)
} else {
(self.seat.clone(), self.focal_point, self.level)
(self.seat.clone(), focal_point, self.level)
}
}
}
@ -1833,6 +1923,24 @@ impl Shell {
.workspaces
.spaces()
.any(|workspace| workspace.animations_going())
|| self.zoom_state.as_ref().is_some_and(|state| {
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()
})
})
}
pub fn update_animations(&mut self) -> HashMap<ClientId, Client> {
@ -1966,26 +2074,124 @@ impl Shell {
}
}
pub fn trigger_zoom(
pub fn trigger_zoom(&mut self, seat: &Seat<State>, level: f64, movement: ZoomMovement) {
if self.zoom_state.is_none() && level == 1. {
return;
}
let previous_level = if let Some(old_state) = self.zoom_state.take() {
if &old_state.seat != seat {
return;
}
old_state.level
} else {
for output in self.outputs() {
if let Some(output_state) = output.user_data().get::<Mutex<OutputZoomState>>() {
*output_state.lock().unwrap() =
OutputZoomState::new(seat, output, movement, level);
}
}
1.0
};
self.zoom_state = Some(ZoomState {
seat: seat.clone(),
level,
movement,
previous_level: Some((previous_level, Instant::now())),
});
}
pub fn update_focal_point(
&mut self,
seat: &Seat<State>,
focal_point: Point<f64, Global>,
level: f64,
original_position: Point<f64, Global>,
movement: ZoomMovement,
) {
if level == 1. {
self.zoom_state.take();
} else {
self.zoom_state = Some(ZoomState {
seat: seat.clone(),
level,
focal_point,
previous: None,
if let Some(state) = self.zoom_state.as_mut() {
if &state.seat != seat {
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();
// 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(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);
}
}
}
}
pub fn zoom_level(&self) -> Option<(Seat<State>, Point<f64, Global>, f64)> {
self.zoom_state.as_ref().map(|s| s.level())
pub fn zoom_level(
&self,
output: Option<&Output>,
) -> Option<(Seat<State>, Point<f64, Global>, f64)> {
self.zoom_state.as_ref().map(|s| s.level(output))
}
fn refresh(
@ -2023,6 +2229,18 @@ impl Shell {
_ => {}
}
if let Some(zoom_state) = self.zoom_state.as_mut() {
if zoom_state
.previous_level
.as_ref()
.is_some_and(|(_, start)| {
Instant::now().duration_since(*start) > ANIMATION_DURATION
})
{
zoom_state.previous_level.take();
}
}
self.workspaces
.refresh(workspace_state, xdg_activation_state);