ltk/input/pointer/
scroll.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

use crate::app::App;
use crate::event_loop::AppData;
use crate::platform::Platform;

use super::super::event::{ Axis, AxisSource, PointerInput };

impl<A: App, P: Platform> AppData<A, P>
{
	pub( super ) fn on_pointer_axis( &mut self, input: &PointerInput, horizontal: Axis, vertical: Axis, source: AxisSource )
	{
		let focus   = input.focus;
		let pos     = self.surface( focus ).to_physical( input.position.0, input.position.1 );
		let notches = vertical.notches;
		if self.scroll_text_under( focus, pos, notches, vertical.distance 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 = source.scroll_factor();
			let step_x = horizontal.distance as f32 * multiplier;
			let step_y = vertical.distance   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 event to the app so embedded content
			// (e.g. a WPE view) can handle scrolling itself:
			// discrete detents first, then the raw axis.
			let notches_x = horizontal.notches;
			let consumed = ( notches != 0.0 || notches_x != 0.0 ) && self.app.on_pointer_wheel( pos.x, pos.y, notches_x, notches );
			if !consumed
			{
				let multiplier = source.scroll_factor();
				let dx = horizontal.distance as f32 * multiplier;
				let dy = vertical.distance   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();
		}
	}
}