ltk/event_loop/text_editing/
ime.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
// 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::platform::Platform;
use crate::tree::find_widget;
use crate::types::Rect;
use crate::widget::text_edit::TextFont;
use crate::widget::WidgetHandlers;

impl<A: App, P: Platform> AppData<A, P>
{
	pub( crate ) fn activate_text_input( &mut self, secure: bool, purpose: crate::osk::Purpose )
	{
		self.text_input_secure  = secure;
		self.text_input_purpose = purpose;
		self.platform.activate_text_input( secure, purpose );
	}

	pub( crate ) fn deactivate_text_input( &mut self )
	{
		self.platform.deactivate_text_input();
	}

	pub( crate ) fn reenable_text_input( &self )
	{
		self.platform.reenable_text_input( self.text_input_secure, self.text_input_purpose );
	}

	/// Follow [`App::external_text_input`] while no ltk text widget holds the focus.
	pub( crate ) fn sync_external_text_input( &mut self )
	{
		if self.focused_text_input( self.keyboard_focus )
		{
			self.external_text_input = None;
			return;
		}
		let wanted = self.app.external_text_input();
		match ( wanted, self.external_text_input )
		{
			( Some( _ ), _ ) if !self.platform.text_input_active() =>
			{
				let purpose = self.app.external_text_purpose();
				self.activate_text_input( false, purpose );
			}
			( Some( new ), Some( old ) ) if new != old =>
			{
				self.text_input_purpose = self.app.external_text_purpose();
				self.reenable_text_input();
			}
			( None, Some( _ ) ) => self.deactivate_text_input(),
			_ => {}
		}
		// The built-in keyboard follows the request itself, not the input method: shown when it starts or renews, hidden when it ends.
		let focus = self.keyboard_focus;
		match ( wanted, self.external_text_input )
		{
			( Some( new ), old ) if old != Some( new ) => self.show_osk_for( focus, self.app.external_text_purpose() ),
			( None, Some( _ ) )                        => self.hide_osk( focus ),
			_                                          => {}
		}
		self.external_text_input = wanted;
	}

	/// Snapshot a focused-widget's geometry needed by the pointer
	/// hit-testers for text editing. Returns `None` when the widget
	/// isn't a TextEdit or its rect is missing — the helper above
	/// short-circuits in that case.
	pub( crate ) fn text_input_geometry(
		&self,
		focus: SurfaceFocus,
		idx:   usize,
	) -> Option<( Rect, String, bool, bool, crate::widget::text::TextAlign, TextFont )>
	{
		let ss = self.surface( focus );
		let widget = find_widget( &ss.frame.widget_rects, idx )?;
		let ( value_handler, multiline, secure, align, font, line_numbers ) = match &widget.handlers
		{
			WidgetHandlers::TextEdit { value, multiline, secure, align, font_size, line_numbers, face, .. } =>
				( value.clone(), *multiline, *secure, *align, TextFont { size: *font_size, face: face.clone() }, *line_numbers ),
			_ => return None,
		};
		// Prefer the *pending* value (typed-but-not-yet-applied) when
		// it exists — that's what the user sees right now and what
		// the cursor measurements should be relative to.
		let value = ss.pending_text_values.get( &idx )
			.cloned()
			.unwrap_or( value_handler );
		let rect = match ss.canvas.as_ref()
		{
			Some( canvas ) => crate::widget::text_edit::gutter::text_rect( canvas, widget.rect, &value, &font, line_numbers ),
			None           => widget.rect,
		};
		Some( ( rect, value, multiline, secure, align, font ) )
	}

	/// The horizontal inner padding override of the text field at `idx`, as its hit-testing needs.
	pub( crate ) fn text_input_pad( &self, focus: SurfaceFocus, idx: usize ) -> Option<f32>
	{
		match &find_widget( &self.surface( focus ).frame.widget_rects, idx )?.handlers
		{
			WidgetHandlers::TextEdit { pad_h, .. } => *pad_h,
			_ => None,
		}
	}
}