use std::{
cell::{Cell, RefCell},
rc::{Rc, Weak},
};
use crate::key_event::KeyEvent;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ImeEditorState {
pub text: String,
pub selection_start: usize,
pub selection_end: usize,
pub composition: Option<(usize, usize)>,
pub single_line: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ImeCaretGeometry {
pub caret_xs: Vec<f32>,
pub top: f32,
pub line_height: f32,
}
pub trait FocusedTextFieldHandler {
fn node_id(&self) -> Option<cranpose_core::NodeId> {
None
}
fn handle_key(&self, event: &KeyEvent) -> bool;
fn insert_text(&self, text: &str);
fn delete_surrounding(&self, before_bytes: usize, after_bytes: usize);
fn copy_selection(&self) -> Option<String>;
fn cut_selection(&self) -> Option<String>;
fn select_all(&self) {}
fn set_composition(&self, text: &str, cursor: Option<(usize, usize)>);
fn finish_composition(&self) {}
fn set_composing_region(&self, start_bytes: usize, end_bytes: usize) {
let _ = (start_bytes, end_bytes);
}
fn set_selection(&self, start_bytes: usize, end_bytes: usize) {
let _ = (start_bytes, end_bytes);
}
fn editor_state(&self) -> Option<ImeEditorState> {
None
}
fn caret_geometry(&self) -> Option<ImeCaretGeometry> {
None
}
}
pub(crate) struct TextFieldFocusState {
focused_field: RefCell<Option<Weak<RefCell<bool>>>>,
focused_handler: RefCell<Option<Rc<dyn FocusedTextFieldHandler>>>,
focused_modal_depth: Cell<usize>,
}
impl TextFieldFocusState {
pub(crate) fn new() -> Self {
Self {
focused_field: RefCell::new(None),
focused_handler: RefCell::new(None),
focused_modal_depth: Cell::new(0),
}
}
fn request_focus(
&self,
is_focused: Rc<RefCell<bool>>,
handler: Rc<dyn FocusedTextFieldHandler>,
modal_depth: usize,
) {
let mut current = self.focused_field.borrow_mut();
if let Some(ref weak) = *current
&& let Some(old_focused) = weak.upgrade()
{
*old_focused.borrow_mut() = false;
}
*is_focused.borrow_mut() = true;
*current = Some(Rc::downgrade(&is_focused));
*self.focused_handler.borrow_mut() = Some(handler);
self.focused_modal_depth.set(modal_depth);
}
fn clear_focus(&self) {
let mut current = self.focused_field.borrow_mut();
if let Some(ref weak) = *current
&& let Some(focused) = weak.upgrade()
{
*focused.borrow_mut() = false;
}
*current = None;
*self.focused_handler.borrow_mut() = None;
self.focused_modal_depth.set(0);
}
fn focused_at_depth(&self, depth: usize) -> bool {
self.has_focused_field() && self.focused_modal_depth.get() == depth
}
fn has_focused_field(&self) -> bool {
if self.focused_field_is_live() {
return true;
}
self.clear_stale_focus();
false
}
fn focused_field_is_live(&self) -> bool {
self.focused_field
.borrow()
.as_ref()
.is_some_and(|weak| weak.upgrade().is_some())
}
fn clear_stale_focus(&self) {
let stale_node = self
.focused_handler
.borrow()
.as_ref()
.and_then(|handler| handler.node_id());
let had_entry = self.focused_field.borrow_mut().take().is_some();
if !had_entry {
return;
}
self.focused_handler.borrow_mut().take();
if let Some(node_id) = stale_node {
crate::schedule_draw_repass(node_id);
}
crate::cursor_animation::stop_cursor_blink();
}
fn focused_handler(&self) -> Option<Rc<dyn FocusedTextFieldHandler>> {
if !self.has_focused_field() {
return None;
}
self.focused_handler.borrow().as_ref().cloned()
}
fn dispatch_key_event(&self, event: &KeyEvent) -> bool {
if let Some(handler) = self.focused_handler() {
handler.handle_key(event)
} else {
false
}
}
fn dispatch_paste(&self, text: &str) -> bool {
if let Some(handler) = self.focused_handler() {
handler.insert_text(text);
true
} else {
false
}
}
fn dispatch_delete_surrounding(&self, before_bytes: usize, after_bytes: usize) -> bool {
if let Some(handler) = self.focused_handler() {
handler.delete_surrounding(before_bytes, after_bytes);
true
} else {
false
}
}
fn dispatch_copy(&self) -> Option<String> {
self.focused_handler()
.and_then(|handler| handler.copy_selection())
}
fn dispatch_cut(&self) -> Option<String> {
self.focused_handler()
.and_then(|handler| handler.cut_selection())
}
fn dispatch_select_all(&self) -> bool {
if let Some(handler) = self.focused_handler() {
handler.select_all();
true
} else {
false
}
}
fn dispatch_ime_preedit(&self, text: &str, cursor: Option<(usize, usize)>) -> bool {
if let Some(handler) = self.focused_handler() {
handler.set_composition(text, cursor);
true
} else {
false
}
}
fn dispatch_ime_finish_composing(&self) -> bool {
if let Some(handler) = self.focused_handler() {
handler.finish_composition();
true
} else {
false
}
}
fn dispatch_ime_set_composing_region(&self, start_bytes: usize, end_bytes: usize) -> bool {
if let Some(handler) = self.focused_handler() {
handler.set_composing_region(start_bytes, end_bytes);
true
} else {
false
}
}
fn dispatch_ime_set_selection(&self, start_bytes: usize, end_bytes: usize) -> bool {
if let Some(handler) = self.focused_handler() {
handler.set_selection(start_bytes, end_bytes);
true
} else {
false
}
}
fn focused_editor_state(&self) -> Option<ImeEditorState> {
self.focused_handler()
.and_then(|handler| handler.editor_state())
}
fn focused_caret_geometry(&self) -> Option<ImeCaretGeometry> {
self.focused_handler()
.and_then(|handler| handler.caret_geometry())
}
}
pub fn request_focus(
is_focused: Rc<RefCell<bool>>,
handler: Rc<dyn FocusedTextFieldHandler>,
modal_depth: usize,
) {
if modal_depth < crate::modal::current_modal_depth() {
return;
}
let previous_field = focused_field_node();
let gaining_field = handler.node_id();
crate::render_state::with_text_field_focus(|state| {
state.request_focus(is_focused, handler, modal_depth);
});
for node_id in [previous_field, gaining_field].into_iter().flatten() {
crate::schedule_draw_repass(node_id);
}
crate::cursor_animation::start_cursor_blink();
crate::text_input_session::notify_text_input_focus_gained();
crate::request_render_invalidation();
}
pub(crate) fn clear_focus_for_closed_modal(depth: usize) {
let owns_focus =
crate::render_state::with_text_field_focus(|state| state.focused_at_depth(depth));
if owns_focus {
clear_focus();
}
}
pub fn clear_focus() {
let previous = focused_field_node();
if let Some(node_id) = previous {
crate::schedule_draw_repass(node_id);
}
crate::render_state::with_text_field_focus(TextFieldFocusState::clear_focus);
if previous.is_some() && crate::focus_dispatch::active_focus_target() == previous {
crate::focus_dispatch::clear_active_focus();
}
crate::cursor_animation::stop_cursor_blink();
crate::text_input_session::notify_text_input_focus_lost();
crate::request_render_invalidation();
}
pub fn focused_field_node() -> Option<cranpose_core::NodeId> {
crate::render_state::with_text_field_focus(|state| {
state
.focused_handler()
.and_then(|handler| handler.node_id())
})
}
pub fn has_focused_field() -> bool {
let has_focus =
crate::render_state::with_text_field_focus(TextFieldFocusState::has_focused_field);
if !has_focus {
crate::text_input_session::notify_text_input_focus_lost();
}
has_focus
}
pub fn dispatch_key_event(event: &KeyEvent) -> bool {
crate::render_state::with_text_field_focus(|state| state.dispatch_key_event(event))
}
pub fn dispatch_paste(text: &str) -> bool {
crate::render_state::with_text_field_focus(|state| state.dispatch_paste(text))
}
pub fn dispatch_delete_surrounding(before_bytes: usize, after_bytes: usize) -> bool {
crate::render_state::with_text_field_focus(|state| {
state.dispatch_delete_surrounding(before_bytes, after_bytes)
})
}
pub fn dispatch_copy() -> Option<String> {
crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_copy)
}
pub fn dispatch_cut() -> Option<String> {
crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_cut)
}
pub fn dispatch_select_all() -> bool {
crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_select_all)
}
pub fn dispatch_ime_preedit(text: &str, cursor: Option<(usize, usize)>) -> bool {
crate::render_state::with_text_field_focus(|state| state.dispatch_ime_preedit(text, cursor))
}
pub fn dispatch_ime_finish_composing() -> bool {
crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_ime_finish_composing)
}
pub fn dispatch_ime_set_composing_region(start_bytes: usize, end_bytes: usize) -> bool {
crate::render_state::with_text_field_focus(|state| {
state.dispatch_ime_set_composing_region(start_bytes, end_bytes)
})
}
pub fn dispatch_ime_set_selection(start_bytes: usize, end_bytes: usize) -> bool {
crate::render_state::with_text_field_focus(|state| {
state.dispatch_ime_set_selection(start_bytes, end_bytes)
})
}
pub fn focused_editor_state() -> Option<ImeEditorState> {
crate::render_state::with_text_field_focus(TextFieldFocusState::focused_editor_state)
}
pub fn focused_caret_geometry() -> Option<ImeCaretGeometry> {
crate::render_state::with_text_field_focus(TextFieldFocusState::focused_caret_geometry)
}
#[cfg(test)]
#[path = "tests/text_field_focus_tests.rs"]
mod tests;