ltk/event_loop/
osk.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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! The built-in keyboard under a focused text field, for a session whose compositor offers no input method.

use std::time::Duration;

use super::app_data::AppData;
use super::surface::{ Insets, SurfaceFocus };
use crate::app::App;
use crate::input::event::KeyInput;
use crate::osk::{ Event, Modifiers, Panel };
use crate::platform::Platform;
use crate::Keysym;
use crate::time::Instant;
use crate::tree::find_handlers;
use crate::types::{ Point, Rect };

/// Fit a surface's panel to the bottom of its `pw` by `ph` pixels, inside the `insets` the system covers; returns the height it takes.
pub( crate ) fn place_osk( osk: &mut Option<Panel>, pw: u32, ph: u32, sf: f32, insets: Insets ) -> f32
{
	let Some( panel ) = osk.as_mut() else { return 0.0 };
	let width  = ( pw as f32 - ( insets.left + insets.right ) * sf ).max( 0.0 );
	let bottom = ph as f32 - insets.bottom * sf;
	let h      = panel.height_within( width, sf, ph as f32 );
	panel.set_rect( Rect { x: insets.left * sf, y: bottom - h, width, height: h }, sf );
	h
}

impl<A: App, P: Platform> AppData<A, P>
{
	fn builtin_keyboard( &self ) -> bool
	{
		self.builtin_osk || self.app.builtin_keyboard()
	}

	/// The panel for the focused text field, laid out for its purpose.
	pub( crate ) fn show_osk( &mut self, focus: SurfaceFocus )
	{
		let ss = self.surface( focus );
		let purpose = ss.focused_idx
			.and_then( |i| find_handlers( &ss.frame.widget_rects, i ) )
			.map( |h| h.input_purpose() )
			.unwrap_or_default();
		self.show_osk_for( focus, purpose );
	}

	/// The panel on `focus` for text of `purpose`, also for an app's own text input ([`App::external_text_input`]).
	pub( crate ) fn show_osk_for( &mut self, focus: SurfaceFocus, purpose: crate::osk::Purpose )
	{
		if !self.builtin_keyboard() { return; }
		let ss = self.surface_mut( focus );
		if ss.osk.is_none()
		{
			ss.osk = Some( Panel::for_language( &crate::platform::language() ) );
		}
		if let Some( panel ) = ss.osk.as_mut() { panel.set_purpose( purpose ); }
		let sf = ss.scale_factor;
		let ( pw, ph ) = ( ss.physical_width(), ss.physical_height() );
		place_osk( &mut ss.osk, pw, ph, sf, ss.insets );
		ss.request_redraw();
	}

	pub( crate ) fn hide_osk( &mut self, focus: SurfaceFocus )
	{
		let ss = self.surface_mut( focus );
		if ss.osk.take().is_some() { ss.request_redraw(); }
	}

	/// A press on the text field that already had the focus brings a hidden keyboard back.
	pub( crate ) fn osk_after_press( &mut self, focus: SurfaceFocus, hit: Option<usize> )
	{
		let Some( idx ) = hit else { return };
		let ss = self.surface( focus );
		let is_text = find_handlers( &ss.frame.widget_rects, idx ).map( |h| h.is_text_input() ).unwrap_or( false );
		if is_text && ss.osk.is_none() && self.press_on_focused { self.show_osk( focus ); }
	}

	/// `true` when the press landed on the panel and was consumed.
	pub( crate ) fn osk_press( &mut self, focus: SurfaceFocus, pos: Point ) -> bool
	{
		let now = Instant::now();
		let Some( event ) = self.surface_mut( focus ).osk.as_mut().and_then( |p| p.press_down( pos, now ) ) else { return false };
		self.apply_osk_event( focus, event );
		true
	}

	/// `true` while a key is held, so the motion is the panel's and not a drag.
	pub( crate ) fn osk_move( &mut self, focus: SurfaceFocus, pos: Point ) -> bool
	{
		let ss = self.surface_mut( focus );
		let Some( panel ) = ss.osk.as_mut().filter( |p| p.pressed().is_some() ) else { return false };
		if panel.press_move( pos ) { ss.request_redraw(); }
		true
	}

	/// `true` when the release ended a key press.
	pub( crate ) fn osk_release( &mut self, focus: SurfaceFocus, pos: Point ) -> bool
	{
		let now = Instant::now();
		let Some( event ) = self.surface_mut( focus ).osk.as_mut().and_then( |p| p.press_up( pos, now ) ) else { return false };
		self.apply_osk_event( focus, event );
		true
	}

	pub( crate ) fn cancel_osk_presses( &mut self )
	{
		for ss in std::iter::once( &mut self.main ).chain( self.overlays.values_mut() )
		{
			if let Some( panel ) = ss.osk.as_mut() { panel.cancel_press(); }
		}
	}

	pub( crate ) fn next_osk_wakeup( &self ) -> Option<Duration>
	{
		let now = Instant::now();
		std::iter::once( &self.main ).chain( self.overlays.values() )
			.filter_map( |ss| ss.osk.as_ref().and_then( Panel::next_deadline ) )
			.map( |deadline| deadline.saturating_duration_since( now ) )
			.min()
	}

	pub( crate ) fn check_osk_deadlines( &mut self )
	{
		let now = Instant::now();
		let mut due: Vec<( SurfaceFocus, Event )> = Vec::new();
		if let Some( event ) = self.main.osk.as_mut().and_then( |p| p.tick( now ) ) { due.push( ( SurfaceFocus::Main, event ) ); }
		for ( id, ss ) in self.overlays.iter_mut()
		{
			if let Some( event ) = ss.osk.as_mut().and_then( |p| p.tick( now ) ) { due.push( ( SurfaceFocus::Overlay( *id ), event ) ); }
		}
		for ( focus, event ) in due { self.apply_osk_event( focus, event ); }
	}

	fn apply_osk_event( &mut self, focus: SurfaceFocus, event: Event )
	{
		if self.external_text_input.is_some() && !self.focused_text_input( focus )
		{
			self.apply_osk_event_to_app( focus, event );
			return;
		}
		match event
		{
			Event::Text( text )          => self.handle_text_insert( focus, &text ),
			Event::Backspace             => self.handle_backspace( focus ),
			Event::Enter                 => self.osk_enter( focus ),
			Event::Key( keysym, mods )   => self.osk_key( focus, keysym, mods ),
			Event::Hide                  => self.hide_osk( focus ),
			Event::Changed               => {}
		}
		self.surface_mut( focus ).request_redraw();
	}

	/// For an app's own text input every key reaches the app as a physical one would, through [`App::on_raw_key_event`].
	fn apply_osk_event_to_app( &mut self, focus: SurfaceFocus, event: Event )
	{
		match event
		{
			Event::Text( text ) =>
			{
				let keysym = text.chars().next().map_or( Keysym::NoSymbol, Keysym::from_char );
				self.osk_app_key( keysym, Some( text ), Modifiers::default() );
			}
			Event::Backspace           => self.osk_app_key( Keysym::BackSpace, None, Modifiers::default() ),
			Event::Enter               => self.osk_app_key( Keysym::Return, None, Modifiers::default() ),
			Event::Key( keysym, mods ) => self.osk_app_key( keysym, None, mods ),
			Event::Hide                => self.hide_osk( focus ),
			Event::Changed             => {}
		}
		self.surface_mut( focus ).request_redraw();
	}

	/// Press and release `keysym` with the panel's modifiers held for it. Without modifiers a key that has a character types it, as on a physical keyboard.
	fn osk_app_key( &mut self, keysym: Keysym, text: Option<String>, mods: Modifiers )
	{
		let text = text.or_else( || ( !mods.any() ).then( || keysym.key_char() ).flatten().map( String::from ) );
		let held = ( self.ctrl_pressed, self.shift_pressed, self.alt_pressed );
		self.modifiers_changed( mods.ctrl, held.1, mods.alt );
		let key = KeyInput::synthetic( keysym, text );
		self.key_press( key.clone() );
		self.key_release( &key );
		self.modifiers_changed( held.0, held.1, held.2 );
	}

	/// A key the panel cannot type as text goes through the same dispatch as a physical one.
	fn osk_key( &mut self, focus: SurfaceFocus, keysym: Keysym, mods: Modifiers )
	{
		let utf8  = ( !mods.any() ).then( || keysym.key_char() ).flatten().map( String::from );
		let event = KeyInput::synthetic( keysym, utf8 );
		let ctrl  = std::mem::replace( &mut self.ctrl_pressed, mods.ctrl );
		self.dispatch_key( focus, event );
		self.ctrl_pressed = ctrl;
	}

	/// Enter as the physical key does it: a newline in a multiline field, otherwise the field's submit.
	fn osk_enter( &mut self, focus: SurfaceFocus )
	{
		let ss = self.surface( focus );
		let Some( idx ) = ss.focused_idx else { return };
		let Some( handlers ) = find_handlers( &ss.frame.widget_rects, idx ) else { return };
		if handlers.is_multiline_text_input()
		{
			self.handle_text_insert( focus, "\n" );
		} else if let Some( msg ) = handlers.submit_msg().or_else( || handlers.press_msg() ) {
			self.pending_msgs.push( msg );
		}
	}
}