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 cosmic_config::ConfigSet; 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 tracing::error; 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, pub(super) show_overlay: bool, 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, previous_point: Option<(Point, Instant)>, element: ZoomElement, } impl OutputZoomState { pub fn new( seat: &Seat, 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.geometry().to_f64().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() }; 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.set_additional_scale(level.min(4.)); OutputZoomState { focal_point, previous_point: None, element, } } pub fn animating_focal_point(&mut self) -> Point { 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 current_focal_point(&mut self) -> Point { 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.set_additional_scale(level.min(4.)); self.element.queue_message(ZoomMessage::Update { level, movement, increment, }); } fn render(&mut self, renderer: &mut R, output: &Output) -> Vec where C: From< as AsRenderElements>::RenderElement>, R: Renderer + ImportMem, R::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 { self.seat.clone() } pub fn animating_focal_point(&self, output: Option<&Output>) -> Point { let active_output = self.seat.active_output(); let output = output.unwrap_or(&active_output); let output_state = output.user_data().get::>().unwrap(); let res = output_state .lock() .unwrap() .animating_focal_point() .to_global(output); res } pub fn current_focal_point(&self, output: Option<&Output>) -> Point { let active_output = self.seat.active_output(); let output = output.unwrap_or(&active_output); let output_state = output.user_data().get::>().unwrap(); let res = output_state .lock() .unwrap() .current_focal_point() .to_global(output); res } pub fn update_focal_point( &mut self, output: &Output, cursor_position: Point, original_position: Point, movement: ZoomMovement, ) { let cursor_position = cursor_position.to_i32_round(); let original_position = original_position.to_i32_round(); let output_geometry = output.geometry(); let mut zoomed_output_geometry = output.zoomed_geometry(self.level).unwrap(); let output_state = output.user_data().get::>().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.to_f64(), ZoomMovement::OnEdge => { if !zoomed_output_geometry .overlaps_or_touches(Rectangle::new(original_position, Size::from((1, 1)))) { 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, output_geometry.loc.x + output_geometry.size.w - 1, ); focal_point.y = focal_point.y.clamp( output_geometry.loc.y, output_geometry.loc.y + output_geometry.size.h - 1, ); output_state_ref.previous_point = Some((output_state_ref.focal_point, Instant::now())); 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) .to_f64() .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 .checked_div(output_geometry.size.w - zoomed_output_geometry.size.w) .unwrap_or(1), ) .to_global(&output); focal_point.x = focal_point.x.clamp( output_geometry.loc.x, output_geometry.loc.x + output_geometry.size.w - 1, ); focal_point.y = focal_point.y.clamp( output_geometry.loc.y, output_geometry.loc.y + output_geometry.size.h - 1, ); output_state_ref.focal_point = focal_point.to_local(&output).to_f64(); } } } pub fn surface_under( &self, output: &Output, pos: Point, ) -> Option<(PointerFocusTarget, Point)> { 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::>().unwrap(); let output_state_ref = output_state.lock().unwrap(); let size = output_state_ref.element.current_size().to_f64().as_local(); let location = Point::::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::::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(renderer: &mut R, output: &Output) -> Vec where C: From< as AsRenderElements>::RenderElement>, R: Renderer + ImportMem, R::TextureId: Send + Clone + 'static, { let output_state = output.user_data().get::>().unwrap(); output_state.lock().unwrap().render(renderer, output) } } fn global_pos_to_screen_space( pos: impl Into>, output: &Output, level: f64, ) -> Point { 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::::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; pub struct ZoomProgram { level: f64, increments: Vec, 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, Serial)>, ) -> cosmic::Task { 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::>().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::::from(( output_geometry.size.w as f64 / 2. - elem_size.w / 2., output_geometry.size.h as f64 / 4. * 3. - elem_size.h / 2., )); let position = Point::<_, Local>::from(( location.x, elem_location.y + elem_size.h / 2., )); std::mem::drop(output_state_ref); let grab = MenuGrab::new( start_data, &seat, vec![ 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" ); } state.common.update_config(); }); }, ) .toggled(movement == ZoomMovement::Continuously), Item::new( crate::fl!("a11y-zoom-move-onedge"), move |handle| { let _ = handle.insert_idle(move |state| { state .common .config .cosmic_conf .accessibility_zoom .view_moves = ZoomMovement::OnEdge; 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(); }); }, ) .toggled(movement == ZoomMovement::OnEdge), Item::new( crate::fl!("a11y-zoom-move-centered"), move |handle| { let _ = handle.insert_idle(move |state| { state .common .config .cosmic_conf .accessibility_zoom .view_moves = ZoomMovement::Centered; 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(); }); }, ) .toggled(movement == ZoomMovement::Centered), Item::Separator, Item::new(crate::fl!("a11y-zoom-settings"), |handle| { let _ = handle.insert_idle(move |state| { state.spawn_command( "cosmic-settings page-accessibility".into(), ); }); }), ] .into_iter(), position.to_global(&output).to_i32_round(), MenuAlignment::horizontally_centered( (elem_size.h / 2.).round() as u32, false, ), Some(zoom_state.level.min(4.)), 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::>().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::::from(( output_geometry.size.w as f64 / 2. - elem_size.w / 2., output_geometry.size.h as f64 / 4. * 3. - elem_size.h / 2., )); let position = Point::<_, Local>::from(( location.x, elem_location.y + (elem_size.h / 2.), )); std::mem::drop(output_state_ref); 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::PREFER_CENTERED, Some(zoom_state.level.min(4.)), 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| { state .common .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(); }); } 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), } impl From for ZoomFocusTarget { fn from(value: ZoomElement) -> Self { ZoomFocusTarget::Main(value) } } impl From> for ZoomFocusTarget { fn from(value: IcedElement) -> Self { ZoomFocusTarget::Menu(value) } } impl PointerTarget for ZoomFocusTarget { fn enter(&self, seat: &Seat, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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 for ZoomFocusTarget { fn down(&self, seat: &Seat, 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, 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, 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, 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, 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, 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, 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(), } } }