// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. use smithay_client_toolkit::seat::pointer::{ PointerEvent, PointerEventKind }; use smithay_client_toolkit::reexports::client:: { protocol::{ wl_pointer, wl_pointer::WlPointer }, Connection, QueueHandle, }; use crate::app::App; use crate::event_loop::{ AppData, SurfaceFocus }; impl AppData { pub( super ) fn on_pointer_axis( &mut self, _conn: &Connection, _qh: &QueueHandle, _pointer: &WlPointer, event: &PointerEvent, ) { let focus = self.focus_for_surface( &event.surface ) .unwrap_or( SurfaceFocus::Main ); let ( horizontal, vertical, source ) = if let PointerEventKind::Axis { horizontal, vertical, source, .. } = event.kind { ( horizontal, vertical, source ) } else { return; }; let pos = self.surface( focus ).to_physical( event.position.0, event.position.1 ); let notches = if vertical.value120 != 0 { vertical.value120 as f32 / 120.0 } else { vertical.discrete as f32 }; if self.scroll_text_under( focus, pos, notches, vertical.absolute as f32 ) { return; } let scroll_hit = { let ss = self.surface( focus ); ss.frame.scroll_rects.iter() .find( |( r, _, _ )| r.contains( pos ) ) .map( |( _, idx, ax )| ( *idx, *ax ) ) }; if let Some( ( scroll_idx, axis ) ) = scroll_hit { let multiplier = match source { Some( wl_pointer::AxisSource::Wheel ) => 10.0, _ => 1.0, }; let step_x = horizontal.absolute as f32 * multiplier; let step_y = vertical.absolute as f32 * multiplier; let ss = self.surface_mut( focus ); let entry = ss.frame.scroll_offsets.entry( scroll_idx ).or_insert( ( 0.0, 0.0 ) ); // Wheels report on a single axis at a time; route to // whichever axis the viewport allows. A pure horizontal // viewport translates a vertical wheel into horizontal // motion (the conventional shift-less wheel-to-strip UX); // `Both` keeps each axis independent. match axis { crate::widget::scroll::ScrollAxis::Vertical => { entry.1 = ( entry.1 + step_y ).max( 0.0 ); } crate::widget::scroll::ScrollAxis::Horizontal => { entry.0 = ( entry.0 + step_x + step_y ).max( 0.0 ); } crate::widget::scroll::ScrollAxis::Both => { entry.0 = ( entry.0 + step_x ).max( 0.0 ); entry.1 = ( entry.1 + step_y ).max( 0.0 ); } } ss.request_redraw(); } else { // No LTK scroll viewport under the cursor — // surface the raw axis to the app so embedded // content (e.g. a WPE view) can handle scrolling // itself. let multiplier = match source { Some( wl_pointer::AxisSource::Wheel ) => 10.0, _ => 1.0, }; let dx = horizontal.absolute as f32 * multiplier; let dy = vertical.absolute as f32 * multiplier; self.app.on_pointer_axis( pos.x, pos.y, dx, dy ); // The hook may mutate view-driving state (a wheel-stepped // carousel, an embedder-scrolled canvas), so rebuild and // repaint like the in-viewport branch above does. self.dirty_caches(); self.surface_mut( focus ).request_redraw(); } } }