cranpose_ui/
text_input_session.rs1use std::cell::{Cell, RefCell};
26use std::rc::Rc;
27
28pub trait PlatformTextInputHandler {
34 fn show_keyboard(&self);
36 fn hide_keyboard(&self);
39}
40
41pub(crate) struct PlatformTextInputState {
43 handler: RefCell<Option<Rc<dyn PlatformTextInputHandler>>>,
44 keyboard_requested: Cell<bool>,
48}
49
50impl PlatformTextInputState {
51 pub(crate) fn new() -> Self {
52 Self {
53 handler: RefCell::new(None),
54 keyboard_requested: Cell::new(false),
55 }
56 }
57
58 fn set_handler(&self, handler: Option<Rc<dyn PlatformTextInputHandler>>) {
59 *self.handler.borrow_mut() = handler;
60 self.keyboard_requested.set(false);
61 }
62
63 fn handler(&self) -> Option<Rc<dyn PlatformTextInputHandler>> {
64 self.handler.borrow().clone()
65 }
66}
67
68pub fn set_platform_text_input_handler(handler: Rc<dyn PlatformTextInputHandler>) {
74 crate::render_state::with_text_input_session(|state| state.set_handler(Some(handler)));
75}
76
77pub fn clear_platform_text_input_handler() {
79 crate::render_state::with_text_input_session(|state| state.set_handler(None));
80}
81
82pub(crate) fn notify_text_input_focus_gained() {
87 let handler = crate::render_state::with_text_input_session(|state| {
88 let handler = state.handler();
89 if handler.is_some() {
90 state.keyboard_requested.set(true);
91 }
92 handler
93 });
94 if let Some(handler) = handler {
97 handler.show_keyboard();
98 }
99}
100
101pub(crate) fn notify_text_input_focus_lost() {
107 let handler = crate::render_state::with_text_input_session(|state| {
108 if !state.keyboard_requested.replace(false) {
109 return None;
110 }
111 state.handler()
112 });
113 if let Some(handler) = handler {
114 handler.hide_keyboard();
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use std::cell::RefCell as StdRefCell;
122
123 #[derive(Default)]
124 struct RecordingHandler {
125 calls: StdRefCell<Vec<&'static str>>,
126 }
127
128 impl PlatformTextInputHandler for RecordingHandler {
129 fn show_keyboard(&self) {
130 self.calls.borrow_mut().push("show");
131 }
132
133 fn hide_keyboard(&self) {
134 self.calls.borrow_mut().push("hide");
135 }
136 }
137
138 fn install_recording_handler() -> Rc<RecordingHandler> {
139 let handler = Rc::new(RecordingHandler::default());
140 set_platform_text_input_handler(handler.clone());
141 handler
142 }
143
144 #[test]
145 fn focus_gained_shows_keyboard() {
146 let _app_context = crate::render_state::app_context_test_scope();
147 let handler = install_recording_handler();
148
149 notify_text_input_focus_gained();
150
151 assert_eq!(*handler.calls.borrow(), vec!["show"]);
152 }
153
154 #[test]
155 fn focus_lost_hides_keyboard_once() {
156 let _app_context = crate::render_state::app_context_test_scope();
157 let handler = install_recording_handler();
158
159 notify_text_input_focus_gained();
160 notify_text_input_focus_lost();
161 notify_text_input_focus_lost();
164
165 assert_eq!(*handler.calls.borrow(), vec!["show", "hide"]);
166 }
167
168 #[test]
169 fn focus_lost_without_prior_show_is_not_forwarded() {
170 let _app_context = crate::render_state::app_context_test_scope();
171 let handler = install_recording_handler();
172
173 notify_text_input_focus_lost();
174
175 assert!(handler.calls.borrow().is_empty());
176 }
177
178 #[test]
179 fn repeated_focus_gain_reshows_keyboard() {
180 let _app_context = crate::render_state::app_context_test_scope();
181 let handler = install_recording_handler();
182
183 notify_text_input_focus_gained();
186 notify_text_input_focus_gained();
187
188 assert_eq!(*handler.calls.borrow(), vec!["show", "show"]);
189 }
190
191 #[test]
192 fn notifications_without_handler_are_noops() {
193 let _app_context = crate::render_state::app_context_test_scope();
194 notify_text_input_focus_gained();
195 notify_text_input_focus_lost();
196 }
197
198 #[test]
199 fn clearing_handler_stops_notifications() {
200 let _app_context = crate::render_state::app_context_test_scope();
201 let handler = install_recording_handler();
202
203 notify_text_input_focus_gained();
204 clear_platform_text_input_handler();
205 notify_text_input_focus_lost();
206
207 assert_eq!(*handler.calls.borrow(), vec!["show"]);
208 }
209}