diff --git a/crates/molotov-core/src/input/ime.rs b/crates/molotov-core/src/input/ime.rs new file mode 100644 index 0000000..3d579bc --- /dev/null +++ b/crates/molotov-core/src/input/ime.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Rect { + pub x: i32, + pub y: i32, + pub width: i32, + pub height: i32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ImeState { + Inactive, + Active, +} + +pub struct ImeManager { + state: ImeState, + cursor_area: Rect, + preedit_text: String, + cursor_pos: usize, +} + +impl Default for ImeManager { + fn default() -> Self { + Self::new() + } +} + +impl ImeManager { + pub fn new() -> Self { + Self { + state: ImeState::Inactive, + cursor_area: Rect::default(), + preedit_text: String::new(), + cursor_pos: 0, + } + } + + pub fn is_active(&self) -> bool { + self.state == ImeState::Active + } + + pub fn start_text_input(&mut self, area: Rect) { + self.state = ImeState::Active; + self.cursor_area = area; + self.preedit_text.clear(); + self.cursor_pos = 0; + } + + pub fn stop_text_input(&mut self) { + self.state = ImeState::Inactive; + self.preedit_text.clear(); + self.cursor_pos = 0; + } + + pub fn set_cursor_area(&mut self, area: Rect) { + self.cursor_area = area; + } + + pub fn cursor_area(&self) -> Rect { + self.cursor_area + } + + pub fn update_preedit(&mut self, text: &str, cursor_pos: usize) { + self.preedit_text = text.to_string(); + self.cursor_pos = cursor_pos.min(self.preedit_text.chars().count()); + } + + pub fn preedit_text(&self) -> &str { + &self.preedit_text + } + + pub fn cursor_pos(&self) -> usize { + self.cursor_pos + } + + pub fn commit_text(&mut self, text: &str) -> String { + self.preedit_text.clear(); + self.cursor_pos = 0; + text.to_string() + } + + pub fn cancel_composition(&mut self) { + self.preedit_text.clear(); + self.cursor_pos = 0; + } +} diff --git a/crates/molotov-core/src/input/mod.rs b/crates/molotov-core/src/input/mod.rs new file mode 100644 index 0000000..8534bf3 --- /dev/null +++ b/crates/molotov-core/src/input/mod.rs @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pub mod ime; diff --git a/crates/molotov-core/src/lib.rs b/crates/molotov-core/src/lib.rs index f00998e..c4b8822 100644 --- a/crates/molotov-core/src/lib.rs +++ b/crates/molotov-core/src/lib.rs @@ -2,6 +2,7 @@ pub mod audio; pub mod dynamic; pub mod elf; +pub mod input; pub mod jni; pub mod linker; pub mod mmap; diff --git a/crates/molotov-core/tests/ime_preedit_test.rs b/crates/molotov-core/tests/ime_preedit_test.rs new file mode 100644 index 0000000..419eec3 --- /dev/null +++ b/crates/molotov-core/tests/ime_preedit_test.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use molotov_core::input::ime::{ImeManager, Rect}; + +#[test] +fn test_ime_preedit_composition_flow() { + let mut ime = ImeManager::new(); + ime.start_text_input(Rect { x: 100, y: 200, width: 20, height: 24 }); + assert!(ime.is_active()); + + // User types in Japanese IME: preedit string "とうきょう" + ime.update_preedit("とうきょう", 4); + assert_eq!(ime.preedit_text(), "とうきょう"); + assert_eq!(ime.cursor_pos(), 4); + + // User selects candidate: commit "東京" + let committed = ime.commit_text("東京"); + assert_eq!(committed, "東京"); + assert_eq!(ime.preedit_text(), "", "Preedit must clear on commit"); + + ime.stop_text_input(); + assert!(!ime.is_active()); +}