ltk/event_loop/text_editing/
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
76
77
78
79
80
81
82
83
84
85
86
87
88
// 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::app_data::AppData;
use crate::event_loop::surface::SurfaceFocus;
use crate::tree::find_widget_at;
use crate::types::Point;
use crate::widget::text_edit::scroll::{ Scrollbar, TextScroll, Window, WHEEL_LINES };
use crate::platform::Platform;

impl<A: App, P: Platform> AppData<A, P>
{
	pub( crate ) fn text_scroll( &self, focus: SurfaceFocus, idx: usize ) -> TextScroll
	{
		self.surface( focus ).frame.text_scroll.get( &idx ).copied().unwrap_or_default()
	}

	/// Scroll the multiline field under `pos` by `notches` of a wheel
	/// or, when there are none, by `logical_px` of touchpad motion.
	/// `false` when no field is there or it cannot move that way, so
	/// the caller can hand the motion to a viewport around it.
	pub( crate ) fn scroll_text_under( &mut self, focus: SurfaceFocus, pos: Point, notches: f32, logical_px: f32 ) -> bool
	{
		let Some( idx ) = find_widget_at( &self.surface( focus ).frame.widget_rects, pos ) else { return false };
		let Some( ( rect, value, true, false, _, font ) ) = self.text_input_geometry( focus, idx ) else { return false };
		let ss         = self.surface( focus );
		let cursor_pos = ss.frame.cursor_state.get( &idx ).copied().unwrap_or( 0 );
		let scroll     = self.text_scroll( focus, idx );
		let Some( canvas ) = ss.canvas.as_ref() else { return false };
		let window     = Window::shown( canvas, rect, &value, &font, cursor_pos, scroll );
		let delta_px   = if notches != 0.0
		{
			notches * WHEEL_LINES * window.metrics.line_h
		} else {
			logical_px * ss.scale_factor
		};
		if !window.can_scroll( delta_px ) { return false; }
		let ( next, moved ) = scroll.scrolled( window.first_line, window.max_first, cursor_pos, delta_px, window.metrics.line_h );
		let ss = self.surface_mut( focus );
		ss.frame.text_scroll.insert( idx, next );
		if moved { ss.request_redraw(); }
		true
	}

	/// Take a press on the scroll bar of the multiline field at `idx`: on the thumb it holds it where it was taken, on the track it brings the thumb's middle there. `false` when the press is elsewhere, for the caller to place the cursor.
	pub( crate ) fn handle_text_scrollbar_press( &mut self, focus: SurfaceFocus, idx: usize, pos: Point ) -> bool
	{
		self.text_scrollbar_grab = None;
		let Some( ( window, bar, cursor_pos ) ) = self.text_scrollbar( focus, idx ) else { return false };
		if !bar.hit.contains( pos ) { return false; }
		let on_thumb = pos.y >= bar.thumb.y && pos.y <= bar.thumb.y + bar.thumb.height;
		let grab     = if on_thumb { pos.y - bar.thumb.y } else { bar.thumb.height / 2.0 };
		self.text_scrollbar_grab = Some( ( focus, idx, grab ) );
		self.scroll_text_to( focus, idx, window.first_line_for( &bar, pos.y - grab ), window.first_line, cursor_pos );
		true
	}

	/// Follow a press that holds the scroll bar of the field at `idx`. `false` when the press does not, for the caller to extend the selection.
	pub( crate ) fn handle_text_scrollbar_drag( &mut self, focus: SurfaceFocus, idx: usize, pos: Point ) -> bool
	{
		let Some( ( grab_focus, grab_idx, grab ) ) = self.text_scrollbar_grab else { return false };
		if grab_focus != focus || grab_idx != idx { return false; }
		if let Some( ( window, bar, cursor_pos ) ) = self.text_scrollbar( focus, idx )
		{
			self.scroll_text_to( focus, idx, window.first_line_for( &bar, pos.y - grab ), window.first_line, cursor_pos );
		}
		true
	}

	pub( crate ) fn text_scrollbar( &self, focus: SurfaceFocus, idx: usize ) -> Option<( Window, Scrollbar, usize )>
	{
		let Some( ( rect, value, true, false, _, font ) ) = self.text_input_geometry( focus, idx ) else { return None };
		let ss         = self.surface( focus );
		let cursor_pos = ss.frame.cursor_state.get( &idx ).copied().unwrap_or( 0 );
		let canvas     = ss.canvas.as_ref()?;
		let window     = Window::shown( canvas, rect, &value, &font, cursor_pos, self.text_scroll( focus, idx ) );
		let bar        = window.scrollbar( canvas, rect )?;
		Some( ( window, bar, cursor_pos ) )
	}

	fn scroll_text_to( &mut self, focus: SurfaceFocus, idx: usize, first_line: usize, shown: usize, cursor_pos: usize )
	{
		let ss = self.surface_mut( focus );
		ss.frame.text_scroll.insert( idx, TextScroll { first_line, cursor_pos, remainder: 0.0 } );
		if first_line != shown { ss.request_redraw(); }
	}
}