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,
|
|
|
|
|
iced::{Alignment, Background, Border, Length, alignment::Vertical},
|
2025-02-13 21:09:13 +01:00
|
|
|
iced_widget, theme,
|
|
|
|
|
widget::{self, icon::Named},
|
|
|
|
|
};
|
|
|
|
|
use cosmic_comp_config::ZoomMovement;
|
2025-02-14 19:14:18 +01:00
|
|
|
use cosmic_config::ConfigSet;
|
2025-02-13 21:09:13 +01:00
|
|
|
use keyframe::{ease, functions::EaseInOutCubic};
|
|
|
|
|
use smithay::{
|
2025-10-16 18:53:57 +02:00
|
|
|
backend::renderer::{ImportMem, Renderer, element::AsRenderElements},
|
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::{
|
|
|
|
|
DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget,
|
|
|
|
|
UpEvent,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
output::Output,
|
|
|
|
|
utils::{IsAlive, Point, Rectangle, Serial, Size},
|
|
|
|
|
};
|
2025-02-14 19:14:18 +01:00
|
|
|
use tracing::error;
|
2025-02-13 21:09:13 +01:00
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
state::State,
|
|
|
|
|
utils::{
|
|
|
|
|
iced::{IcedElement, Program},
|
|
|
|
|
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>,
|
|
|
|
|
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 => {
|
2025-10-16 13:50:32 +02:00
|
|
|
cursor_position.to_local(output)
|
2025-02-13 21:09:13 +01:00
|
|
|
}
|
|
|
|
|
ZoomMovement::Centered => {
|
2025-02-20 14:37:53 +01:00
|
|
|
let mut zoomed_output_geometry = output.geometry().to_f64().downscale(level);
|
2025-02-13 21:09:13 +01:00
|
|
|
zoomed_output_geometry.loc =
|
|
|
|
|
cursor_position - zoomed_output_geometry.size.downscale(2.).to_point();
|
|
|
|
|
|
|
|
|
|
let mut focal_point = zoomed_output_geometry
|
|
|
|
|
.loc
|
2025-10-16 13:50:32 +02:00
|
|
|
.to_local(output)
|
2025-02-13 21:09:13 +01:00
|
|
|
.upscale(level)
|
2025-10-16 13:50:32 +02:00
|
|
|
.to_global(output);
|
2025-02-13 21:09:13 +01:00
|
|
|
focal_point.x = focal_point.x.clamp(
|
2025-10-16 13:50:32 +02:00
|
|
|
output_geometry.loc.x,
|
2025-12-19 18:59:31 +01:00
|
|
|
(output_geometry.loc.x + output_geometry.size.w).next_down(),
|
2025-02-13 21:09:13 +01:00
|
|
|
);
|
|
|
|
|
focal_point.y = focal_point.y.clamp(
|
2025-10-16 13:50:32 +02:00
|
|
|
output_geometry.loc.y,
|
2025-12-19 18:59:31 +01:00
|
|
|
(output_geometry.loc.y + output_geometry.size.h).next_down(),
|
2025-02-13 21:09:13 +01:00
|
|
|
);
|
2025-10-16 13:50:32 +02:00
|
|
|
focal_point.to_local(output)
|
2025-02-13 21:09:13 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} 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));
|
2025-02-14 18:34:52 +01:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 17:59:07 +01:00
|
|
|
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(
|
|
|
|
|
EaseInOutCubic,
|
|
|
|
|
EasePoint(*old_point),
|
|
|
|
|
EasePoint(self.focal_point),
|
|
|
|
|
percentage,
|
|
|
|
|
)
|
|
|
|
|
.0
|
|
|
|
|
} else {
|
|
|
|
|
self.focal_point
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 17:59:07 +01:00
|
|
|
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(EaseInOutCubic, *old_level, self.level, percentage)
|
|
|
|
|
} 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;
|
2025-02-14 18:34:52 +01:00
|
|
|
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, C>(&mut self, renderer: &mut R, output: &Output) -> Vec<C>
|
|
|
|
|
where
|
|
|
|
|
C: From<<IcedElement<ZoomProgram> as AsRenderElements<R>>::RenderElement>,
|
|
|
|
|
R: Renderer + 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
|
|
|
|
|
.render_elements(renderer, location, scale.fractional_scale().into(), 1.0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2025-02-17 17:59:07 +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
|
2025-02-17 17:59:07 +01:00
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.animating_focal_point()
|
2026-02-23 16:25:06 +01:00
|
|
|
.to_global(output)
|
2025-02-17 17:59:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2025-02-17 17:59:07 +01:00
|
|
|
.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>,
|
|
|
|
|
original_position: Point<f64, Global>,
|
|
|
|
|
movement: ZoomMovement,
|
|
|
|
|
) {
|
2025-02-17 17:59:52 +01:00
|
|
|
let cursor_position = cursor_position.to_i32_round();
|
|
|
|
|
let original_position = original_position.to_i32_round();
|
|
|
|
|
let output_geometry = output.geometry();
|
2025-03-25 17:31:48 +01:00
|
|
|
let mut zoomed_output_geometry = output.zoomed_geometry().unwrap();
|
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();
|
|
|
|
|
|
|
|
|
|
// 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 {
|
2025-02-17 17:59:52 +01:00
|
|
|
ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position.to_f64(),
|
2025-02-13 21:09:13 +01:00
|
|
|
ZoomMovement::OnEdge => {
|
2025-02-17 17:59:52 +01:00
|
|
|
if !zoomed_output_geometry
|
2025-03-25 18:13:51 +01:00
|
|
|
.overlaps_or_touches(Rectangle::new(original_position, Size::from((16, 16))))
|
2025-02-17 17:59:52 +01:00
|
|
|
{
|
2025-10-16 13:50:32 +02:00
|
|
|
zoomed_output_geometry.loc = cursor_position.to_global(output)
|
2025-02-17 17:59:52 +01:00
|
|
|
- zoomed_output_geometry.size.downscale(2).to_point();
|
2025-02-13 21:09:13 +01:00
|
|
|
let mut focal_point = zoomed_output_geometry
|
|
|
|
|
.loc
|
2025-10-16 13:50:32 +02:00
|
|
|
.to_local(output)
|
2025-02-13 21:09:13 +01:00
|
|
|
.upscale(
|
|
|
|
|
output_geometry.size.w
|
|
|
|
|
/ (output_geometry.size.w - zoomed_output_geometry.size.w),
|
|
|
|
|
)
|
2025-10-16 13:50:32 +02:00
|
|
|
.to_global(output);
|
2025-02-13 21:09:13 +01:00
|
|
|
focal_point.x = focal_point.x.clamp(
|
2025-02-17 17:59:52 +01:00
|
|
|
output_geometry.loc.x,
|
|
|
|
|
output_geometry.loc.x + output_geometry.size.w - 1,
|
2025-02-13 21:09:13 +01:00
|
|
|
);
|
|
|
|
|
focal_point.y = focal_point.y.clamp(
|
2025-02-17 17:59:52 +01:00
|
|
|
output_geometry.loc.y,
|
|
|
|
|
output_geometry.loc.y + output_geometry.size.h - 1,
|
2025-02-13 21:09:13 +01:00
|
|
|
);
|
|
|
|
|
output_state_ref.previous_point =
|
|
|
|
|
Some((output_state_ref.focal_point, Instant::now()));
|
2025-10-16 13:50:32 +02:00
|
|
|
output_state_ref.focal_point = focal_point.to_local(output).to_f64();
|
|
|
|
|
} 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)
|
2025-02-17 17:59:52 +01:00
|
|
|
.to_f64()
|
2025-03-25 17:31:48 +01:00
|
|
|
.upscale(output_state_ref.level);
|
2025-02-13 21:09:13 +01:00
|
|
|
diff.x = diff.x.clamp(
|
|
|
|
|
output_geometry.loc.x as f64,
|
2025-12-19 18:59:31 +01:00
|
|
|
((output_geometry.loc.x + output_geometry.size.w) as f64).next_down(),
|
2025-02-13 21:09:13 +01:00
|
|
|
);
|
|
|
|
|
diff.y = diff.y.clamp(
|
|
|
|
|
output_geometry.loc.y as f64,
|
2025-12-19 18:59:31 +01:00
|
|
|
((output_geometry.loc.y + output_geometry.size.h) as f64).next_down(),
|
2025-02-13 21:09:13 +01:00
|
|
|
);
|
2025-10-16 13:50:32 +02:00
|
|
|
diff -= output_state_ref.focal_point.to_global(output);
|
2025-02-13 21:09:13 +01:00
|
|
|
|
|
|
|
|
output_state_ref.focal_point += diff.as_logical().as_local();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ZoomMovement::Centered => {
|
2025-10-16 13:50:32 +02:00
|
|
|
zoomed_output_geometry.loc = cursor_position.to_global(output)
|
2025-02-17 17:59:52 +01:00
|
|
|
- zoomed_output_geometry.size.downscale(2).to_point();
|
2025-02-13 21:09:13 +01:00
|
|
|
|
|
|
|
|
let mut focal_point = zoomed_output_geometry
|
|
|
|
|
.loc
|
2025-10-16 13:50:32 +02:00
|
|
|
.to_local(output)
|
2025-02-13 21:09:13 +01:00
|
|
|
.upscale(
|
2025-02-20 14:37:53 +01:00
|
|
|
output_geometry
|
|
|
|
|
.size
|
|
|
|
|
.w
|
|
|
|
|
.checked_div(output_geometry.size.w - zoomed_output_geometry.size.w)
|
|
|
|
|
.unwrap_or(1),
|
2025-02-13 21:09:13 +01:00
|
|
|
)
|
2025-10-16 13:50:32 +02:00
|
|
|
.to_global(output);
|
2025-02-13 21:09:13 +01:00
|
|
|
focal_point.x = focal_point.x.clamp(
|
2025-02-17 17:59:52 +01:00
|
|
|
output_geometry.loc.x,
|
|
|
|
|
output_geometry.loc.x + output_geometry.size.w - 1,
|
2025-02-13 21:09:13 +01:00
|
|
|
);
|
|
|
|
|
focal_point.y = focal_point.y.clamp(
|
2025-02-17 17:59:52 +01:00
|
|
|
output_geometry.loc.y,
|
|
|
|
|
output_geometry.loc.y + output_geometry.size.h - 1,
|
2025-02-13 21:09:13 +01:00
|
|
|
);
|
2025-10-16 13:50:32 +02:00
|
|
|
output_state_ref.focal_point = focal_point.to_local(output).to_f64();
|
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, C>(renderer: &mut R, output: &Output) -> Vec<C>
|
|
|
|
|
where
|
|
|
|
|
C: From<<IcedElement<ZoomProgram> as AsRenderElements<R>>::RenderElement>,
|
|
|
|
|
R: Renderer + 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.component;
|
|
|
|
|
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| {
|
2025-05-20 17:41:27 +02:00
|
|
|
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| {
|
2025-05-20 17:41:27 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2025-05-20 17:41:27 +02:00
|
|
|
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,
|
2025-02-14 18:34:52 +01:00
|
|
|
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;
|
2025-02-14 18:34:52 +01:00
|
|
|
std::mem::drop(output_state_ref);
|
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(),
|
2025-02-14 18:34:52 +01:00
|
|
|
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(),
|
|
|
|
|
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)
|
|
|
|
|
{
|
2025-05-20 17:41:27 +02:00
|
|
|
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,
|
2025-02-14 18:34:52 +01:00
|
|
|
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;
|
2025-02-14 18:34:52 +01:00
|
|
|
std::mem::drop(output_state_ref);
|
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();
|
2025-12-22 00:42:08 +02: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-13 21:09:13 +01:00
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
position.to_global(&output).to_i32_round(),
|
2025-02-14 18:34:52 +01:00
|
|
|
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(),
|
|
|
|
|
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::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 {
|
|
|
|
|
fn down(&self, seat: &Seat<State>, 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<State>, 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<State>, 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<State>, 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<State>, 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<State>, 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<State>,
|
|
|
|
|
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(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|