shell: Add zoom UI

This commit is contained in:
Victoria Brekenfeld 2025-02-13 21:09:13 +01:00 committed by Victoria Brekenfeld
parent 55e4dd7c0f
commit f72d2b91f3
10 changed files with 1247 additions and 261 deletions

View file

@ -3,6 +3,7 @@ use crate::shell::{CosmicMappedRenderElement, WorkspaceRenderElement};
use smithay::{
backend::renderer::{
element::{
memory::MemoryRenderBufferRenderElement,
surface::WaylandSurfaceRenderElement,
texture::TextureRenderElement,
utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement},
@ -36,6 +37,7 @@ where
RelocateRenderElement<RescaleRenderElement<TextureRenderElement<GlesTexture>>>,
>,
),
Zoom(MemoryRenderBufferRenderElement<R>),
#[cfg(feature = "debug")]
Egui(TextureRenderElement<GlesTexture>),
}
@ -54,6 +56,7 @@ where
CosmicElement::MoveGrab(elem) => elem.id(),
CosmicElement::AdditionalDamage(elem) => elem.id(),
CosmicElement::Mirror(elem) => elem.id(),
CosmicElement::Zoom(elem) => elem.id(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.id(),
}
@ -67,6 +70,7 @@ where
CosmicElement::MoveGrab(elem) => elem.current_commit(),
CosmicElement::AdditionalDamage(elem) => elem.current_commit(),
CosmicElement::Mirror(elem) => elem.current_commit(),
CosmicElement::Zoom(elem) => elem.current_commit(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.current_commit(),
}
@ -80,6 +84,7 @@ where
CosmicElement::MoveGrab(elem) => elem.src(),
CosmicElement::AdditionalDamage(elem) => elem.src(),
CosmicElement::Mirror(elem) => elem.src(),
CosmicElement::Zoom(elem) => elem.src(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.src(),
}
@ -93,6 +98,7 @@ where
CosmicElement::MoveGrab(elem) => elem.geometry(scale),
CosmicElement::AdditionalDamage(elem) => elem.geometry(scale),
CosmicElement::Mirror(elem) => elem.geometry(scale),
CosmicElement::Zoom(elem) => elem.geometry(scale),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.geometry(scale),
}
@ -106,6 +112,7 @@ where
CosmicElement::MoveGrab(elem) => elem.location(scale),
CosmicElement::AdditionalDamage(elem) => elem.location(scale),
CosmicElement::Mirror(elem) => elem.location(scale),
CosmicElement::Zoom(elem) => elem.location(scale),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.location(scale),
}
@ -119,6 +126,7 @@ where
CosmicElement::MoveGrab(elem) => elem.transform(),
CosmicElement::AdditionalDamage(elem) => elem.transform(),
CosmicElement::Mirror(elem) => elem.transform(),
CosmicElement::Zoom(elem) => elem.transform(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.transform(),
}
@ -136,6 +144,7 @@ where
CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit),
CosmicElement::AdditionalDamage(elem) => elem.damage_since(scale, commit),
CosmicElement::Mirror(elem) => elem.damage_since(scale, commit),
CosmicElement::Zoom(elem) => elem.damage_since(scale, commit),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.damage_since(scale, commit),
}
@ -149,6 +158,7 @@ where
CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale),
CosmicElement::AdditionalDamage(elem) => elem.opaque_regions(scale),
CosmicElement::Mirror(elem) => elem.opaque_regions(scale),
CosmicElement::Zoom(elem) => elem.opaque_regions(scale),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.opaque_regions(scale),
}
@ -162,6 +172,7 @@ where
CosmicElement::MoveGrab(elem) => elem.alpha(),
CosmicElement::AdditionalDamage(elem) => elem.alpha(),
CosmicElement::Mirror(elem) => elem.alpha(),
CosmicElement::Zoom(elem) => elem.alpha(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.alpha(),
}
@ -175,6 +186,7 @@ where
CosmicElement::MoveGrab(elem) => elem.kind(),
CosmicElement::AdditionalDamage(elem) => elem.kind(),
CosmicElement::Mirror(elem) => elem.kind(),
CosmicElement::Zoom(elem) => elem.kind(),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => elem.kind(),
}
@ -219,6 +231,7 @@ where
};
elem
}
CosmicElement::Zoom(elem) => elem.draw(frame, src, dst, damage, opaque_regions),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => {
let elem = {
@ -254,6 +267,7 @@ where
_ => None,
}
}
CosmicElement::Zoom(elem) => elem.underlying_storage(renderer),
#[cfg(feature = "debug")]
CosmicElement::Egui(elem) => {
let glow_renderer = renderer.glow_renderer_mut();
@ -295,6 +309,17 @@ where
}
}
impl<R> From<MemoryRenderBufferRenderElement<R>> for CosmicElement<R>
where
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
CosmicMappedRenderElement<R>: RenderElement<R>,
{
fn from(value: MemoryRenderBufferRenderElement<R>) -> Self {
Self::Zoom(value)
}
}
#[cfg(feature = "debug")]
impl<R> From<TextureRenderElement<GlesTexture>> for CosmicElement<R>
where

View file

@ -18,6 +18,7 @@ use crate::{
focus::{render_input_order, target::WindowGroup, Stage},
grabs::{SeatMenuGrabState, SeatMoveGrabState},
layout::tiling::ANIMATION_DURATION,
zoom::ZoomState,
CosmicMappedRenderElement, OverviewMode, SeatExt, Trigger, WorkspaceDelta,
WorkspaceRenderElement,
},
@ -404,7 +405,7 @@ pub enum CursorMode {
pub fn cursor_elements<'a, 'frame, R>(
renderer: &mut R,
seats: impl Iterator<Item = &'a Seat<State>>,
zoom_level: Option<(Seat<State>, Point<f64, Global>, f64)>,
zoom_state: Option<&ZoomState>,
theme: &Theme,
now: Time<Monotonic>,
output: &Output,
@ -417,8 +418,13 @@ where
CosmicMappedRenderElement<R>: RenderElement<R>,
{
let scale = output.current_scale().fractional_scale();
let (focal_point, zoom_scale) = zoom_level
.map(|(_, p, s)| (p.to_local(&output), s))
let (focal_point, zoom_scale) = zoom_state
.map(|state| {
(
state.focal_point(Some(&output)).to_local(&output),
state.animating_level(),
)
})
.unwrap_or_else(|| ((0., 0.).into(), 1.));
let mut elements = Vec::new();
@ -495,23 +501,32 @@ where
}));
}
if let Some(grab_elements) = seat
if let Some((grab_elements, should_scale)) = seat
.user_data()
.get::<SeatMenuGrabState>()
.unwrap()
.lock()
.unwrap()
.as_ref()
.map(|state| state.render::<CosmicMappedRenderElement<R>, R>(renderer, output))
.map(|state| {
(
state.render::<CosmicMappedRenderElement<R>, R>(renderer, output),
!state.is_in_screen_space(),
)
})
{
elements.extend(grab_elements.into_iter().map(|elem| {
CosmicElement::MoveGrab(RescaleRenderElement::from_element(
elem,
focal_point
.as_logical()
.to_physical(output.current_scale().fractional_scale())
.to_i32_round(),
zoom_scale,
if should_scale {
focal_point
.as_logical()
.to_physical(output.current_scale().fractional_scale())
.to_i32_round()
} else {
Point::from((0, 0))
},
if should_scale { zoom_scale } else { 1.0 },
))
}));
}
@ -595,14 +610,14 @@ where
} else {
ElementFilter::All
};
let zoom_level = shell.read().unwrap().zoom_level(Some(&output));
let zoom_state = shell.read().unwrap().zoom_state().cloned();
#[allow(unused_mut)]
let workspace_elements = workspace_elements(
_gpu,
renderer,
shell,
zoom_level,
zoom_state.as_ref(),
now,
output,
previous_workspace,
@ -625,7 +640,7 @@ pub fn workspace_elements<R>(
_gpu: Option<&DrmNode>,
renderer: &mut R,
shell: &Arc<RwLock<Shell>>,
zoom_level: Option<(Seat<State>, Point<f64, Global>, f64)>,
zoom_level: Option<&ZoomState>,
now: Time<Monotonic>,
output: &Output,
previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>,
@ -642,18 +657,16 @@ where
{
let mut elements = Vec::new();
let theme = shell.read().unwrap().theme().clone();
let seats = shell
.read()
.unwrap()
.seats
.iter()
.cloned()
.collect::<Vec<_>>();
let shell_ref = shell.read().unwrap();
let seats = shell_ref.seats.iter().cloned().collect::<Vec<_>>();
if seats.is_empty() {
return Ok(Vec::new());
}
let theme = shell_ref.theme().clone();
let scale = output.current_scale().fractional_scale();
// we don't want to hold a shell lock across `cursor_elements`,
// that is prone to deadlock with the main-thread on some grabs.
std::mem::drop(shell_ref);
elements.extend(cursor_elements(
renderer,
@ -667,7 +680,6 @@ where
));
let shell = shell.read().unwrap();
let overview = shell.overview_mode();
let (resize_mode, resize_indicator) = shell.resize_mode();
let resize_indicator = resize_indicator.map(|indicator| (resize_mode, indicator));
@ -715,7 +727,12 @@ where
.as_logical()
.to_physical_precise_round(scale);
let (focal_point, zoom_scale) = zoom_level
.map(|(_, p, s)| (p.to_local(&output), s))
.map(|state| {
(
state.focal_point(Some(&output)).to_local(&output),
state.animating_level(),
)
})
.unwrap_or_else(|| ((0., 0.).into(), 1.));
let crop_to_output = |element: WorkspaceRenderElement<R>| {
@ -741,6 +758,9 @@ where
element_filter,
|stage| {
match stage {
Stage::ZoomUI => {
elements.extend(ZoomState::render(renderer, output));
}
Stage::SessionLock(lock_surface) => {
elements.extend(
session_lock_elements(renderer, output, lock_surface)
@ -1006,6 +1026,7 @@ where
.zip(previous_idx)
.map(|((w, start), idx)| (w.handle, idx, start));
let workspace = (workspace.handle, idx);
let zoom_state = shell_ref.zoom_state().cloned();
std::mem::drop(shell_ref);
let element_filter = if workspace_overview_is_open(output) {
@ -1013,7 +1034,6 @@ where
} else {
ElementFilter::All
};
let zoom_level = shell.read().unwrap().zoom_level(Some(&output));
let result = render_workspace(
gpu,
@ -1023,7 +1043,7 @@ where
age,
None,
shell,
zoom_level,
zoom_state.as_ref(),
now,
output,
previous_workspace,
@ -1136,7 +1156,7 @@ pub fn render_workspace<'d, R, Target, OffTarget>(
age: usize,
additional_damage: Option<Vec<Rectangle<i32, Logical>>>,
shell: &Arc<RwLock<Shell>>,
zoom_level: Option<(Seat<State>, Point<f64, Global>, f64)>,
zoom_level: Option<&ZoomState>,
now: Time<Monotonic>,
output: &Output,
previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>,

View file

@ -323,6 +323,7 @@ fn format_pointer_focus(focus: Option<PointerFocusTarget>) -> String {
window.surface().title()
),
Some(ResizeFork(_)) => String::from("Resize UI"),
Some(ZoomUI(_)) => String::from("Zoom UI"),
None => format!("None"),
}
}

View file

@ -21,6 +21,7 @@ use crate::{
floating::ResizeGrabMarker,
tiling::{NodeDesc, SwapWindowGrab, TilingLayout},
},
zoom::ZoomState,
SeatExt, Trigger,
},
utils::{float::NextDown, prelude::*, quirks::workspace_overview_is_open},
@ -1187,7 +1188,8 @@ impl State {
return;
};
let position = transform_output_mapped_position(&output, &event);
let position =
transform_output_mapped_position(&output, &event, shell.zoom_state());
let under = State::surface_under(position, &output, &mut *shell)
.map(|(target, pos)| (target, pos.as_logical()));
@ -1218,7 +1220,8 @@ impl State {
return;
};
let position = transform_output_mapped_position(&output, &event);
let position =
transform_output_mapped_position(&output, &event, shell.zoom_state());
let under = State::surface_under(position, &output, &mut *shell)
.map(|(target, pos)| (target, pos.as_logical()));
@ -1302,7 +1305,8 @@ impl State {
return;
};
let position = transform_output_mapped_position(&output, &event);
let position =
transform_output_mapped_position(&output, &event, shell.zoom_state());
let under = State::surface_under(position, &output, &mut *shell)
.map(|(target, pos)| (target, pos.as_logical()));
@ -1366,7 +1370,8 @@ impl State {
return;
};
let position = transform_output_mapped_position(&output, &event);
let position =
transform_output_mapped_position(&output, &event, shell.zoom_state());
let under = State::surface_under(position, &output, &mut *shell)
.map(|(target, pos)| (target, pos.as_logical()));
@ -1939,6 +1944,7 @@ impl State {
element_filter,
|stage| {
match stage {
Stage::ZoomUI => {}
Stage::SessionLock(lock_surface) => {
return ControlFlow::Break(Ok(lock_surface
.cloned()
@ -2067,6 +2073,15 @@ impl State {
element_filter,
|stage| {
match stage {
Stage::ZoomUI => {
if let Some(zoom_state) = shell.zoom_state() {
if let Some((target, loc)) =
zoom_state.surface_under(output, global_pos)
{
return ControlFlow::Break(Ok(Some((target, loc))));
}
}
}
Stage::SessionLock(lock_surface) => {
return ControlFlow::Break(Ok(lock_surface.map(|surface| {
(
@ -2196,13 +2211,19 @@ fn cursor_sessions_for_output<'a>(
})
}
fn transform_output_mapped_position<'a, B, E>(output: &Output, event: &E) -> Point<f64, Global>
fn transform_output_mapped_position<'a, B, E>(
output: &Output,
event: &E,
zoom_state: Option<&ZoomState>,
) -> Point<f64, Global>
where
B: InputBackend,
E: AbsolutePositionEvent<B>,
B::Device: 'static,
{
let geometry = output.geometry();
let geometry = zoom_state
.and_then(|state| output.zoomed_geometry(state.current_level()))
.unwrap_or_else(|| output.geometry());
let transform = output.current_transform();
let size = transform
.invert()

View file

@ -21,6 +21,7 @@ use crate::{
};
pub enum Stage<'a> {
ZoomUI,
SessionLock(Option<&'a LockSurface>),
LayerPopup {
layer: LayerSurface,
@ -69,6 +70,10 @@ fn render_input_order_internal<R: 'static>(
element_filter: ElementFilter,
mut callback: impl FnMut(Stage) -> ControlFlow<Result<R, OutputNoMode>, ()>,
) -> ControlFlow<Result<R, OutputNoMode>, ()> {
if shell.zoom_state.is_some() {
callback(Stage::ZoomUI)?;
}
// Session Lock
if let Some(session_lock) = &shell.session_lock {
return callback(Stage::SessionLock(session_lock.surfaces.get(output)));

View file

@ -4,6 +4,7 @@ use crate::{
shell::{
element::{CosmicMapped, CosmicStack, CosmicWindow},
layout::tiling::ResizeForkTarget,
zoom::ZoomFocusTarget,
CosmicSurface, SeatExt,
},
utils::prelude::*,
@ -41,6 +42,7 @@ pub enum PointerFocusTarget {
StackUI(CosmicStack),
WindowUI(CosmicWindow),
ResizeFork(ResizeForkTarget),
ZoomUI(ZoomFocusTarget),
}
#[derive(Debug, Clone, PartialEq)]
@ -188,6 +190,7 @@ impl IsAlive for PointerFocusTarget {
PointerFocusTarget::StackUI(e) => e.alive(),
PointerFocusTarget::WindowUI(e) => e.alive(),
PointerFocusTarget::ResizeFork(f) => f.alive(),
PointerFocusTarget::ZoomUI(_) => true,
}
}
}
@ -233,6 +236,7 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::StackUI(u) => PointerTarget::enter(u, seat, data, event),
PointerFocusTarget::WindowUI(u) => PointerTarget::enter(u, seat, data, event),
PointerFocusTarget::ResizeFork(f) => PointerTarget::enter(f, seat, data, event),
PointerFocusTarget::ZoomUI(e) => PointerTarget::enter(e, seat, data, event),
}
}
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
@ -262,6 +266,7 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::StackUI(u) => PointerTarget::motion(u, seat, data, event),
PointerFocusTarget::WindowUI(u) => PointerTarget::motion(u, seat, data, event),
PointerFocusTarget::ResizeFork(f) => PointerTarget::motion(f, seat, data, event),
PointerFocusTarget::ZoomUI(e) => PointerTarget::motion(e, seat, data, event),
}
}
fn relative_motion(&self, seat: &Seat<State>, data: &mut State, event: &RelativeMotionEvent) {
@ -274,6 +279,7 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(f) => {
PointerTarget::relative_motion(f, seat, data, event)
}
PointerFocusTarget::ZoomUI(e) => PointerTarget::relative_motion(e, seat, data, event),
}
}
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
@ -284,6 +290,7 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::StackUI(u) => PointerTarget::button(u, seat, data, event),
PointerFocusTarget::WindowUI(u) => PointerTarget::button(u, seat, data, event),
PointerFocusTarget::ResizeFork(f) => PointerTarget::button(f, seat, data, event),
PointerFocusTarget::ZoomUI(e) => PointerTarget::button(e, seat, data, event),
}
}
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
@ -294,6 +301,7 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::StackUI(u) => PointerTarget::axis(u, seat, data, frame),
PointerFocusTarget::WindowUI(u) => PointerTarget::axis(u, seat, data, frame),
PointerFocusTarget::ResizeFork(f) => PointerTarget::axis(f, seat, data, frame),
PointerFocusTarget::ZoomUI(e) => PointerTarget::axis(e, seat, data, frame),
}
}
fn frame(&self, seat: &Seat<State>, data: &mut State) {
@ -304,6 +312,7 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::StackUI(u) => PointerTarget::frame(u, seat, data),
PointerFocusTarget::WindowUI(u) => PointerTarget::frame(u, seat, data),
PointerFocusTarget::ResizeFork(f) => PointerTarget::frame(f, seat, data),
PointerFocusTarget::ZoomUI(e) => PointerTarget::frame(e, seat, data),
}
}
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
@ -322,6 +331,7 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::StackUI(u) => PointerTarget::leave(u, seat, data, serial, time),
PointerFocusTarget::WindowUI(u) => PointerTarget::leave(u, seat, data, serial, time),
PointerFocusTarget::ResizeFork(f) => PointerTarget::leave(f, seat, data, serial, time),
PointerFocusTarget::ZoomUI(e) => PointerTarget::leave(e, seat, data, serial, time),
}
}
fn gesture_swipe_begin(
@ -343,6 +353,9 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(f) => {
PointerTarget::gesture_swipe_begin(f, seat, data, event)
}
PointerFocusTarget::ZoomUI(e) => {
PointerTarget::gesture_swipe_begin(e, seat, data, event)
}
}
}
fn gesture_swipe_update(
@ -364,6 +377,9 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(f) => {
PointerTarget::gesture_swipe_update(f, seat, data, event)
}
PointerFocusTarget::ZoomUI(e) => {
PointerTarget::gesture_swipe_update(e, seat, data, event)
}
}
}
fn gesture_swipe_end(
@ -385,6 +401,7 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(f) => {
PointerTarget::gesture_swipe_end(f, seat, data, event)
}
PointerFocusTarget::ZoomUI(e) => PointerTarget::gesture_swipe_end(e, seat, data, event),
}
}
fn gesture_pinch_begin(
@ -406,6 +423,9 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(f) => {
PointerTarget::gesture_pinch_begin(f, seat, data, event)
}
PointerFocusTarget::ZoomUI(e) => {
PointerTarget::gesture_pinch_begin(e, seat, data, event)
}
}
}
fn gesture_pinch_update(
@ -427,6 +447,9 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(f) => {
PointerTarget::gesture_pinch_update(f, seat, data, event)
}
PointerFocusTarget::ZoomUI(e) => {
PointerTarget::gesture_pinch_update(e, seat, data, event)
}
}
}
fn gesture_pinch_end(
@ -448,6 +471,7 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(f) => {
PointerTarget::gesture_pinch_end(f, seat, data, event)
}
PointerFocusTarget::ZoomUI(e) => PointerTarget::gesture_pinch_end(e, seat, data, event),
}
}
fn gesture_hold_begin(
@ -469,6 +493,9 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(f) => {
PointerTarget::gesture_hold_begin(f, seat, data, event)
}
PointerFocusTarget::ZoomUI(e) => {
PointerTarget::gesture_hold_begin(e, seat, data, event)
}
}
}
fn gesture_hold_end(&self, seat: &Seat<State>, data: &mut State, event: &GestureHoldEndEvent) {
@ -483,6 +510,7 @@ impl PointerTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(f) => {
PointerTarget::gesture_hold_end(f, seat, data, event)
}
PointerFocusTarget::ZoomUI(e) => PointerTarget::gesture_hold_end(e, seat, data, event),
}
}
}
@ -498,6 +526,7 @@ impl TouchTarget<State> for PointerFocusTarget {
}
PointerFocusTarget::StackUI(stack) => TouchTarget::down(stack, seat, data, event, seq),
PointerFocusTarget::ResizeFork(fork) => TouchTarget::down(fork, seat, data, event, seq),
PointerFocusTarget::ZoomUI(elem) => TouchTarget::down(elem, seat, data, event, seq),
}
}
@ -509,6 +538,7 @@ impl TouchTarget<State> for PointerFocusTarget {
PointerFocusTarget::WindowUI(window) => TouchTarget::up(window, seat, data, event, seq),
PointerFocusTarget::StackUI(stack) => TouchTarget::up(stack, seat, data, event, seq),
PointerFocusTarget::ResizeFork(fork) => TouchTarget::up(fork, seat, data, event, seq),
PointerFocusTarget::ZoomUI(elem) => TouchTarget::up(elem, seat, data, event, seq),
}
}
@ -526,6 +556,7 @@ impl TouchTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(fork) => {
TouchTarget::motion(fork, seat, data, event, seq)
}
PointerFocusTarget::ZoomUI(elem) => TouchTarget::motion(elem, seat, data, event, seq),
}
}
@ -537,6 +568,7 @@ impl TouchTarget<State> for PointerFocusTarget {
PointerFocusTarget::WindowUI(window) => TouchTarget::frame(window, seat, data, seq),
PointerFocusTarget::StackUI(stack) => TouchTarget::frame(stack, seat, data, seq),
PointerFocusTarget::ResizeFork(fork) => TouchTarget::frame(fork, seat, data, seq),
PointerFocusTarget::ZoomUI(elem) => TouchTarget::frame(elem, seat, data, seq),
}
}
@ -548,6 +580,7 @@ impl TouchTarget<State> for PointerFocusTarget {
PointerFocusTarget::WindowUI(window) => TouchTarget::cancel(window, seat, data, seq),
PointerFocusTarget::StackUI(stack) => TouchTarget::cancel(stack, seat, data, seq),
PointerFocusTarget::ResizeFork(fork) => TouchTarget::cancel(fork, seat, data, seq),
PointerFocusTarget::ZoomUI(elem) => TouchTarget::cancel(elem, seat, data, seq),
}
}
@ -563,6 +596,7 @@ impl TouchTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(fork) => {
TouchTarget::shape(fork, seat, data, event, seq)
}
PointerFocusTarget::ZoomUI(elem) => TouchTarget::shape(elem, seat, data, event, seq),
}
}
@ -586,6 +620,9 @@ impl TouchTarget<State> for PointerFocusTarget {
PointerFocusTarget::ResizeFork(fork) => {
TouchTarget::orientation(fork, seat, data, event, seq)
}
PointerFocusTarget::ZoomUI(elem) => {
TouchTarget::orientation(elem, seat, data, event, seq)
}
}
}
}
@ -734,7 +771,8 @@ impl WaylandFocus for PointerFocusTarget {
PointerFocusTarget::WlSurface { surface, .. } => Cow::Borrowed(surface),
PointerFocusTarget::ResizeFork(_)
| PointerFocusTarget::StackUI(_)
| PointerFocusTarget::WindowUI(_) => {
| PointerFocusTarget::WindowUI(_)
| PointerFocusTarget::ZoomUI(_) => {
return None;
}
})
@ -751,7 +789,7 @@ impl WaylandFocus for PointerFocusTarget {
.wl_surface()
.map(|s| s.id().same_client_as(object_id))
.unwrap_or(false),
PointerFocusTarget::ResizeFork(_) => false,
PointerFocusTarget::ResizeFork(_) | PointerFocusTarget::ZoomUI(_) => false,
}
}
}

View file

@ -10,10 +10,7 @@ use std::{
};
use wayland_backend::server::ClientId;
use crate::{
utils::{float::NextDown, tween::EasePoint},
wayland::{handlers::data_device, protocols::workspace::WorkspaceCapabilities},
};
use crate::wayland::{handlers::data_device, protocols::workspace::WorkspaceCapabilities};
use cosmic_comp_config::{
workspace::{WorkspaceLayout, WorkspaceMode},
TileBehavior, ZoomConfig, ZoomMovement,
@ -85,9 +82,11 @@ pub mod grabs;
pub mod layout;
mod seats;
mod workspace;
pub mod zoom;
pub use self::element::{CosmicMapped, CosmicMappedRenderElement, CosmicSurface};
pub use self::seats::*;
pub use self::workspace::*;
use self::zoom::{OutputZoomState, ZoomState};
use self::{
element::{
@ -248,116 +247,6 @@ pub struct PendingLayer {
pub output: Output,
}
struct ZoomState {
seat: Seat<State>,
level: f64,
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, 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(),
focal_point,
ease(EaseInOutCubic, *old_level, self.level, percentage),
)
} else {
(self.seat.clone(), focal_point, self.level)
}
}
}
#[derive(Debug)]
pub struct Shell {
pub workspaces: Workspaces,
@ -1279,6 +1168,20 @@ impl Common {
&self.xdg_activation_state,
);
if let Some(state) = shell.zoom_state.as_ref() {
output.user_data().insert_if_missing_threadsafe(|| {
Mutex::new(OutputZoomState::new(
&state.seat,
output,
state.level,
state.increment,
state.movement,
self.event_loop_handle.clone(),
shell.theme.clone(),
))
});
}
std::mem::drop(shell);
self.refresh(); // fixes indicies of any moved workspaces
}
@ -1941,18 +1844,8 @@ impl Shell {
state.previous_level.is_some()
|| self.outputs().any(|o| {
o.user_data()
.get_or_insert_threadsafe(|| {
Mutex::new(OutputZoomState::new(
&state.seat,
o,
state.movement,
state.level,
))
})
.lock()
.unwrap()
.previous_point
.is_some()
.get::<Mutex<OutputZoomState>>()
.is_some_and(|state| state.lock().unwrap().is_animating())
})
})
}
@ -2092,8 +1985,9 @@ impl Shell {
&mut self,
seat: &Seat<State>,
level: f64,
movement: ZoomMovement,
zoom_config: &ZoomConfig,
animate: bool,
loop_handle: &LoopHandle<'static, State>,
) {
if self.zoom_state.is_none() && level == 1. {
return;
@ -2104,13 +1998,38 @@ impl Shell {
return;
}
old_state.level
for output in self.outputs() {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
output_state.lock().unwrap().update(
level,
zoom_config.view_moves,
zoom_config.increment,
);
}
old_state.animating_level()
} else {
for output in self.outputs() {
if let Some(output_state) = output.user_data().get::<Mutex<OutputZoomState>>() {
*output_state.lock().unwrap() =
OutputZoomState::new(seat, output, movement, level);
}
let output_state = output.user_data().get_or_insert_threadsafe(|| {
Mutex::new(OutputZoomState::new(
seat,
output,
level,
zoom_config.increment,
zoom_config.view_moves,
loop_handle.clone(),
self.theme.clone(),
))
});
*output_state.lock().unwrap() = OutputZoomState::new(
seat,
output,
level,
zoom_config.increment,
zoom_config.view_moves,
loop_handle.clone(),
self.theme.clone(),
);
}
1.
};
@ -2118,7 +2037,8 @@ impl Shell {
self.zoom_state = Some(ZoomState {
seat: seat.clone(),
level,
movement,
increment: zoom_config.increment,
movement: zoom_config.view_moves,
previous_level: animate.then_some((previous_level, Instant::now())),
});
}
@ -2134,103 +2054,19 @@ impl Shell {
return;
}
let output = seat.active_output();
let output_state = output.user_data().get_or_insert_threadsafe(|| {
Mutex::new(OutputZoomState::new(seat, &output, movement, state.level))
});
let mut output_state_ref = output_state.lock().unwrap();
let cursor_position = seat.get_pointer().unwrap().current_location().as_global();
// animate movement type changes
if state.movement != movement {
output_state_ref.previous_point =
Some((output_state_ref.focal_point, Instant::now()));
state.movement = movement;
}
let cursor_position = seat
.get_pointer()
.unwrap()
.current_location()
.as_global()
.to_local(&output);
match movement {
ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position,
ZoomMovement::OnEdge => {
let output_geometry = output.geometry().to_f64();
let mut zoomed_output_geometry = output_geometry;
zoomed_output_geometry.loc -= output_state_ref.focal_point.to_global(&output);
zoomed_output_geometry = zoomed_output_geometry.downscale(state.level);
zoomed_output_geometry.loc += output_state_ref.focal_point.to_global(&output);
if !zoomed_output_geometry.contains(original_position) {
zoomed_output_geometry.loc = cursor_position.to_global(&output)
- zoomed_output_geometry.size.downscale(2.).to_point();
let mut focal_point = zoomed_output_geometry
.loc
.to_local(&output)
.upscale(state.level)
.to_global(&output);
focal_point.x = focal_point.x.clamp(
output_geometry.loc.x as f64,
((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
focal_point.y = focal_point.y.clamp(
output_geometry.loc.y as f64,
((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
output_state_ref.previous_point =
Some((output_state_ref.focal_point, Instant::now()));
output_state_ref.focal_point = focal_point.to_local(&output);
} else if !zoomed_output_geometry.contains(cursor_position.to_global(&output)) {
let mut diff = output_state_ref.focal_point.to_global(&output)
+ (cursor_position.to_global(&output) - original_position)
.upscale(state.level);
diff.x = diff.x.clamp(
output_geometry.loc.x as f64,
((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
diff.y = diff.y.clamp(
output_geometry.loc.y as f64,
((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
diff -= output_state_ref.focal_point.to_global(&output);
output_state_ref.focal_point += diff.as_logical().as_local();
}
}
ZoomMovement::Centered => {
let output_geometry = output.geometry().to_f64();
let mut zoomed_output_geometry = output_geometry;
zoomed_output_geometry = zoomed_output_geometry.downscale(state.level);
zoomed_output_geometry.loc = cursor_position.to_global(&output)
- zoomed_output_geometry.size.downscale(2.).to_point();
let mut focal_point = zoomed_output_geometry
.loc
.to_local(&output)
.upscale(state.level)
.to_global(&output);
focal_point.x = focal_point.x.clamp(
output_geometry.loc.x as f64,
((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
focal_point.y = focal_point.y.clamp(
output_geometry.loc.y as f64,
((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
output_state_ref.focal_point = focal_point.to_local(&output);
}
}
state.update_focal_point(
&seat.active_output(),
cursor_position,
original_position,
movement,
);
}
}
pub fn zoom_level(
&self,
output: Option<&Output>,
) -> Option<(Seat<State>, Point<f64, Global>, f64)> {
self.zoom_state.as_ref().map(|s| s.level(output))
pub fn zoom_state(&self) -> Option<&ZoomState> {
self.zoom_state.as_ref()
}
fn refresh(
@ -2282,6 +2118,18 @@ impl Shell {
if zoom_state.level == 1. && zoom_state.previous_level.is_none() {
self.zoom_state.take();
}
if self.zoom_state.is_some() {
for output in self.outputs() {
output
.user_data()
.get::<Mutex<OutputZoomState>>()
.unwrap()
.lock()
.unwrap()
.refresh();
}
}
}
self.workspaces

999
src/shell/zoom.rs Normal file
View file

@ -0,0 +1,999 @@
use std::{sync::Mutex, time::Instant};
use calloop::LoopHandle;
use cosmic::{
iced::{alignment::Vertical, Alignment, Background, Border, Length},
iced_widget, theme,
widget::{self, icon::Named},
Apply,
};
use cosmic_comp_config::ZoomMovement;
use keyframe::{ease, functions::EaseInOutCubic};
use smithay::{
backend::renderer::{element::AsRenderElements, ImportMem, Renderer},
desktop::space::SpaceElement,
input::{
pointer::{
AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent,
},
touch::{
DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget,
UpEvent,
},
Seat,
},
output::Output,
utils::{IsAlive, Point, Rectangle, Serial, Size},
};
use crate::{
state::State,
utils::{
float::NextDown,
iced::{IcedElement, Program},
prelude::*,
tween::EasePoint,
},
};
use super::{
check_grab_preconditions,
focus::target::PointerFocusTarget,
grabs::{ContextMenu, Item, MenuAlignment, MenuGrab},
ANIMATION_DURATION,
};
#[derive(Debug, Clone)]
pub struct ZoomState {
pub(super) seat: Seat<State>,
pub(super) level: f64,
pub(super) increment: u32,
pub(super) movement: ZoomMovement,
pub(super) previous_level: Option<(f64, Instant)>,
}
#[derive(Debug)]
pub struct OutputZoomState {
focal_point: Point<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 => {
cursor_position.to_local(&output)
}
ZoomMovement::Centered => {
let mut zoomed_output_geometry =
output.zoomed_geometry(level).unwrap().to_f64();
zoomed_output_geometry.loc =
cursor_position - zoomed_output_geometry.size.downscale(2.).to_point();
let mut focal_point = zoomed_output_geometry
.loc
.to_local(&output)
.upscale(level)
.to_global(&output);
focal_point.x = focal_point.x.clamp(
output_geometry.loc.x as f64,
((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
focal_point.y = focal_point.y.clamp(
output_geometry.loc.y as f64,
((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
focal_point.to_local(&output)
}
}
} else {
(output_geometry.size.w / 2., output_geometry.size.h / 2.).into()
};
let program = ZoomProgram::new(level, movement, increment);
let element = IcedElement::new(program, Size::default(), loop_handle, theme);
let mut size = element.minimum_size();
size.w = (size.w + 32/*TODO: figure out why iced is calculating too little*/)
.min(output_geometry.size.w.round() as i32);
element.set_activate(true);
element.resize(size);
element.output_enter(output, Rectangle::new(Point::from((0, 0)), size));
element.refresh();
OutputZoomState {
focal_point,
previous_point: None,
element,
}
}
pub fn focal_point(&mut self) -> Point<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
}
}
pub fn is_animating(&self) -> bool {
self.previous_point.is_some()
}
pub fn refresh(&self) {
self.element.refresh()
}
pub fn update(&self, level: f64, movement: ZoomMovement, increment: u32) {
self.element.queue_message(ZoomMessage::Update {
level,
movement,
increment,
});
}
fn render<R, C>(&mut self, renderer: &mut R, output: &Output) -> Vec<C>
where
C: From<<IcedElement<ZoomProgram> as AsRenderElements<R>>::RenderElement>,
R: Renderer + ImportMem,
<R as Renderer>::TextureId: Send + Clone + 'static,
{
let size = self.element.current_size().to_f64();
let output_geo = output.geometry().to_f64();
let scale = output.current_scale();
let location = Point::from((
output_geo.size.w / 2. - size.w / 2.,
output_geo.size.h / 4. * 3. - size.h / 2.,
))
.to_physical(scale.fractional_scale())
.to_i32_round();
self.element
.render_elements(renderer, location, scale.fractional_scale().into(), 1.0)
}
}
impl ZoomState {
pub fn current_level(&self) -> f64 {
self.level
}
pub fn animating_level(&self) -> f64 {
if let Some((old_level, start)) = self.previous_level.as_ref() {
let percentage = Instant::now().duration_since(*start).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32;
ease(EaseInOutCubic, *old_level, self.level, percentage)
} else {
self.level
}
}
pub fn current_seat(&self) -> Seat<State> {
self.seat.clone()
}
pub fn 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();
let res = output_state.lock().unwrap().focal_point().to_global(output);
res
}
pub fn update_focal_point(
&mut self,
output: &Output,
cursor_position: Point<f64, Global>,
original_position: Point<f64, Global>,
movement: ZoomMovement,
) {
let output_geometry = output.geometry().to_f64();
let mut zoomed_output_geometry = output.zoomed_geometry(self.level).unwrap().to_f64();
let output_state = output.user_data().get::<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 {
ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position,
ZoomMovement::OnEdge => {
if !zoomed_output_geometry.contains(original_position) {
zoomed_output_geometry.loc = cursor_position.to_global(&output)
- zoomed_output_geometry.size.downscale(2.).to_point();
let mut focal_point = zoomed_output_geometry
.loc
.to_local(&output)
.upscale(
output_geometry.size.w
/ (output_geometry.size.w - zoomed_output_geometry.size.w),
)
.to_global(&output);
focal_point.x = focal_point.x.clamp(
output_geometry.loc.x as f64,
((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
focal_point.y = focal_point.y.clamp(
output_geometry.loc.y as f64,
((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
output_state_ref.previous_point =
Some((output_state_ref.focal_point, Instant::now()));
output_state_ref.focal_point = focal_point.to_local(&output);
} else if !zoomed_output_geometry.contains(cursor_position.to_global(&output)) {
let mut diff = output_state_ref.focal_point.to_global(&output)
+ (cursor_position.to_global(&output) - original_position)
.upscale(self.level);
diff.x = diff.x.clamp(
output_geometry.loc.x as f64,
((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
diff.y = diff.y.clamp(
output_geometry.loc.y as f64,
((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
diff -= output_state_ref.focal_point.to_global(&output);
output_state_ref.focal_point += diff.as_logical().as_local();
}
}
ZoomMovement::Centered => {
zoomed_output_geometry.loc = cursor_position.to_global(&output)
- zoomed_output_geometry.size.downscale(2.).to_point();
let mut focal_point = zoomed_output_geometry
.loc
.to_local(&output)
.upscale(
output_geometry.size.w
/ (output_geometry.size.w - zoomed_output_geometry.size.w),
)
.to_global(&output);
focal_point.x = focal_point.x.clamp(
output_geometry.loc.x as f64,
((output_geometry.loc.x + output_geometry.size.w) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
focal_point.y = focal_point.y.clamp(
output_geometry.loc.y as f64,
((output_geometry.loc.y + output_geometry.size.h) as f64).next_lower(), // FIXME: Replace with f64::next_down when stable
);
output_state_ref.focal_point = focal_point.to_local(&output);
}
}
}
pub fn surface_under(
&self,
output: &Output,
pos: Point<f64, Global>,
) -> Option<(PointerFocusTarget, Point<f64, Global>)> {
let output_geometry = output.geometry();
let zoomed_output_geometry = output.zoomed_geometry(self.level).unwrap().to_f64();
let local_pos = global_pos_to_screen_space(pos, output, self.level);
let output_state = output.user_data().get::<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...
let scaled_loc = location.downscale(self.level);
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...
let diff = (pos - global_loc).upscale(self.level - 1.);
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,
<R as Renderer>::TextureId: Send + Clone + 'static,
{
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,
level: f64,
) -> Point<f64, Local> {
let pos = pos.into();
let zoomed_output_geometry = output.zoomed_geometry(level).unwrap().to_f64();
// lets try to get the global cursor position into screen space
let relative_to_zoom_geo = Point::<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 {
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()
.unwrap()
.seats
.last_active()
.clone();
let increment =
state.common.config.cosmic_conf.accessibility_zoom.increment as f64 / 100.0;
state.update_zoom(&seat, -increment, true);
});
}
ZoomMessage::Increase => {
let _ = loop_handle.insert_idle(|state| {
let seat = state
.common
.shell
.read()
.unwrap()
.seats
.last_active()
.clone();
let increment =
state.common.config.cosmic_conf.accessibility_zoom.increment as f64 / 100.0;
state.update_zoom(&seat, increment, true);
});
}
ZoomMessage::More => {
let movement = self.movement;
if let Some((seat, serial)) = last_seat.cloned() {
let _ = loop_handle.insert_idle(move |state| {
if let Some(start_data) =
check_grab_preconditions(&seat, Some(serial), None)
{
let shell = state.common.shell.read().unwrap();
let output = seat.active_output();
if let Some(zoom_state) = shell.zoom_state() {
let location = global_pos_to_screen_space(
start_data.location().as_global(),
&output,
zoom_state.current_level(),
);
let output_geometry = output.geometry();
let output_state =
output.user_data().get::<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,
));
let grab = MenuGrab::new(
start_data,
&seat,
vec![
Item::new("View moves with pointer", move |handle| {
let _ = handle.insert_idle(move |state| {
state
.common
.config
.cosmic_conf
.accessibility_zoom
.view_moves = ZoomMovement::Continuously;
state.common.update_config();
// TODO: Write config
});
})
.toggled(movement == ZoomMovement::Continuously),
Item::new(
"View moves when pointer reaches edge",
move |handle| {
let _ = handle.insert_idle(move |state| {
state
.common
.config
.cosmic_conf
.accessibility_zoom
.view_moves = ZoomMovement::OnEdge;
state.common.update_config();
// TODO: Write config
});
},
)
.toggled(movement == ZoomMovement::OnEdge),
Item::new(
"View moves to keep pointer centered",
move |handle| {
let _ = handle.insert_idle(move |state| {
state
.common
.config
.cosmic_conf
.accessibility_zoom
.view_moves = ZoomMovement::Centered;
state.common.update_config();
// TODO: Write config
});
},
)
.toggled(movement == ZoomMovement::Centered),
Item::new("Magnifier settings...", |_| { /* TODO */ }),
]
.into_iter(),
position.to_global(&output).to_i32_round(),
MenuAlignment::HORIZONTALLY_CENTERED,
true,
state.common.event_loop_handle.clone(),
state.common.theme.clone(),
);
std::mem::drop(shell);
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
seat.get_pointer().unwrap().set_grab(
state,
grab,
serial,
Focus::Clear,
);
}
}
}
});
}
}
ZoomMessage::Increment => {
if let Some((seat, serial)) = last_seat.cloned() {
let increments = self.increments.clone();
let _ = loop_handle.insert_idle(move |state| {
if let Some(start_data) =
check_grab_preconditions(&seat, Some(serial), None)
{
let shell = state.common.shell.read().unwrap();
let output = seat.active_output();
if let Some(zoom_state) = shell.zoom_state() {
let location = global_pos_to_screen_space(
start_data.location().as_global(),
&output,
zoom_state.current_level(),
);
let output_geometry = output.geometry();
let output_state =
output.user_data().get::<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,
));
let grab = MenuGrab::new(
start_data,
&seat,
increments.into_iter().map(|val| {
Item::new(format!("{}%", val), move |handle| {
let _ = handle.insert_idle(move |state| {
state
.common
.config
.cosmic_conf
.accessibility_zoom
.increment = val;
state.common.update_config();
// TODO: Write config
});
})
}),
position.to_global(&output).to_i32_round(),
MenuAlignment::CENTERED,
true,
state.common.event_loop_handle.clone(),
state.common.theme.clone(),
);
std::mem::drop(shell);
if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(state, grab, serial);
} else {
seat.get_pointer().unwrap().set_grab(
state,
grab,
serial,
Focus::Clear,
);
}
}
}
});
}
/*
let new_increment = self.increments[idx];
let _ = loop_handle.insert_idle(move |state| {
state.common.config.cosmic_conf.accessibility_zoom.increment = new_increment;
// TODO: Write config
});
*/
}
ZoomMessage::Close => {
let _ = loop_handle.insert_idle(|state| {
let seat = state
.common
.shell
.read()
.unwrap()
.seats
.last_active()
.clone();
state.common.shell.write().unwrap().trigger_zoom(
&seat,
1.0,
&state.common.config.cosmic_conf.accessibility_zoom,
true,
&state.common.event_loop_handle,
);
});
}
ZoomMessage::Update {
level,
increment,
movement,
} => {
self.level = level;
self.movement = movement;
if let Some(pos) = self.increments.iter().position(|val| *val == increment) {
self.increment_idx = pos;
} else {
let mut increments = vec![25, 50, 100, 150, 200];
if !increments.contains(&increment) {
increments.push(increment);
}
increments.sort();
self.increment_idx =
increments.iter().position(|val| *val == increment).unwrap();
self.increments = increments;
}
}
}
cosmic::Task::none()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ZoomFocusTarget {
Main(ZoomElement),
Menu(IcedElement<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(),
}
}
}

View file

@ -20,6 +20,7 @@ pub trait PointExt<C: Coordinate> {
pub trait PointGlobalExt<C: Coordinate> {
fn to_local(self, output: &Output) -> Point<C, Local>;
fn to_zoomed(self, output: &Output, level: f64) -> Point<C, Local>;
fn as_logical(self) -> Point<C, Logical>;
}
@ -65,6 +66,14 @@ impl<C: Coordinate> PointGlobalExt<C> for Point<C, Global> {
(C::from_f64(point.x), C::from_f64(point.y)).into()
}
fn to_zoomed(self, output: &Output, level: f64) -> Point<C, Local> {
let zoomed_output_geometry = output.zoomed_geometry(level).unwrap();
let point = (self.to_f64() - zoomed_output_geometry.loc.to_f64())
.upscale(level)
.as_logical();
(C::from_f64(point.x), C::from_f64(point.y)).into()
}
fn as_logical(self) -> Point<C, Logical> {
(self.x, self.y).into()
}

View file

@ -5,10 +5,13 @@ use smithay::{
};
pub use super::geometry::*;
use crate::config::{AdaptiveSync, OutputConfig, OutputState};
pub use crate::shell::{SeatExt, Shell, Workspace};
pub use crate::state::{Common, State};
pub use crate::wayland::handlers::xdg_shell::popup::update_reactive_popups;
use crate::{
config::{AdaptiveSync, OutputConfig, OutputState},
shell::zoom::OutputZoomState,
};
use std::{
cell::{Ref, RefCell, RefMut},
@ -20,6 +23,8 @@ use std::{
pub trait OutputExt {
fn geometry(&self) -> Rectangle<i32, Global>;
fn zoomed_geometry(&self, level: f64) -> Option<Rectangle<i32, Global>>;
fn adaptive_sync(&self) -> AdaptiveSync;
fn set_adaptive_sync(&self, vrr: AdaptiveSync);
fn adaptive_sync_support(&self) -> Option<Support>;
@ -52,6 +57,21 @@ impl OutputExt for Output {
.as_global()
}
fn zoomed_geometry(&self, level: f64) -> Option<Rectangle<i32, Global>> {
let output_geometry = self.geometry();
let output_state = self.user_data().get::<Mutex<OutputZoomState>>()?;
let mut output_state_ref = output_state.lock().unwrap();
let focal_point = output_state_ref.focal_point().to_global(self);
let mut zoomed_output_geo = output_geometry.to_f64();
zoomed_output_geo.loc -= focal_point;
zoomed_output_geo = zoomed_output_geo.downscale(level);
zoomed_output_geo.loc += focal_point;
Some(zoomed_output_geo.to_i32_round())
}
fn adaptive_sync(&self) -> AdaptiveSync {
self.user_data()
.get::<Vrr>()