ltk/event_loop/
setup.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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! Building the runtime's state for an app, and the event sources every backend installs on its loop.

use std::collections::HashMap;

use crate::a11y::A11yState;
use crate::app::App;
use crate::platform::Platform;
use crate::types::Point;

use super::{ AppData, RunError, SurfaceFocus, SurfaceState };
use super::perf::PerfState;
use super::sources::Sources;

impl<A: App, P: Platform> AppData<A, P>
{
	/// `app` on `platform` with `main` as its only surface, nothing focused, pressed or queued.
	pub( crate ) fn new( app: A, platform: P, main: SurfaceState<A::Message, P::Surface>, sources: Sources<Self>, builtin_osk: bool, a11y: Option<A11yState> ) -> Self
	{
		let fullscreen = app.start_fullscreen();
		Self
		{
			app,
			platform,
			pointer_pos:             Point::default(),
			current_cursor_shape:    None,
			builtin_osk,
			text_input_secure:       false,
			text_input_purpose:      crate::osk::Purpose::Normal,
			external_text_input:     None,
			primary_selection:       String::new(),
			primary_field:           None,
			pending_primary_paste:   None,
			a11y,
			shift_pressed:           false,
			ctrl_pressed:            false,
			alt_pressed:             false,
			sources,
			compositor_repeat_rate:  0,
			compositor_repeat_delay: 0,
			key_repeat:              None,
			button_repeat:           None,
			clipboard:               String::new(),
			last_press_time:         None,
			last_press_pos:          None,
			debug_layout:            std::env::var( "LTK_DEBUG_LAYOUT" ).is_ok(),
			pending_msgs:            Vec::new(),
			pending_drag_inits:      Vec::new(),
			tooltip_pending:         None,
			tooltip_visible:         None,
			exit_requested:          false,
			applied_fullscreen:      Some( fullscreen ),
			main,
			overlays:                HashMap::new(),
			secondaries:             Vec::new(),
			pointer_focus:           SurfaceFocus::Main,
			keyboard_focus:          SurfaceFocus::Main,
			press_on_focused:        false,
			text_scrollbar_grab:     None,
			reported_cursors:        HashMap::new(),
			touch_focus:             HashMap::new(),
			cached_view:             None,
			cached_overlays:         None,
			view_dirty:              true,
			overlays_dirty:          true,
			first_frame_committed:   false,
			focus_retry:             None,
			perf:                    PerfState::new(),
		}
	}

	/// Let the app send messages from any thread, follow the desktop's text scale and wake on [`App::poll_interval`].
	pub( crate ) fn install_sources( &mut self ) -> Result<(), RunError>
	{
		// Only queued: the run loop's `App::invalidate_after` decides which surfaces the message redraws.
		let sender = self.sources.channel( |msg, data: &mut Self| data.pending_msgs.push( msg ) )
			.map_err( |e| RunError::EventLoop( format!( "channel insert_source: {e}" ) ) )?;
		self.app.set_channel_sender( sender );

		// Font resolution multiplies by `text_scale()` at paint time, so a new factor only needs a repaint.
		#[ cfg( not( target_arch = "wasm32" ) ) ]
		{
			let tx = self.sources.channel( |factor: f32, data: &mut Self|
			{
				if ( factor - crate::types::text_scale() ).abs() < f32::EPSILON { return; }
				crate::types::set_text_scale( factor );
				data.dirty_caches();
				data.main.request_redraw();
				for ss in data.overlays.values_mut()
				{
					ss.request_redraw();
				}
			} )
				.map_err( |e| RunError::EventLoop( format!( "text-scale insert_source: {e}" ) ) )?;
			#[ cfg( unix ) ]
			super::text_scale::spawn_watcher( tx );
			#[ cfg( feature = "windows" ) ]
			crate::platform::windows::text_scale::spawn_watcher( tx );
		}

		if let Some( dur ) = self.app.poll_interval()
		{
			self.perf.warn_poll_interval( dur );
			self.sources.insert_timer( dur, |data: &mut Self|
			{
				let msgs = data.app.poll_external();
				data.pending_msgs.extend( msgs );
				Some( data.app.poll_interval().unwrap_or( std::time::Duration::from_secs( 1 ) ) )
			} )
				.map_err( |e| RunError::EventLoop( format!( "poll timer insert_source: {e}" ) ) )?;
		}
		Ok( () )
	}
}