Răsfoiți Sursa

app: fix race condition in handling android IME events in BaseEdit

jkds 7 luni în urmă
părinte
comite
bcb10050df

+ 2 - 0
bin/app/src/android/textinput/gametextinput.rs

@@ -25,6 +25,7 @@ use std::{
 
 
 use super::{AndroidTextInputState, SharedStatePtr};
 use super::{AndroidTextInputState, SharedStatePtr};
 
 
+macro_rules! t { ($($arg:tt)*) => { trace!(target: "android::textinput::gametextinput", $($arg)*); } }
 macro_rules! w { ($($arg:tt)*) => { warn!(target: "android::textinput::gametextinput", $($arg)*); } }
 macro_rules! w { ($($arg:tt)*) => { warn!(target: "android::textinput::gametextinput", $($arg)*); } }
 
 
 pub const SPAN_UNDEFINED: i32 = -1;
 pub const SPAN_UNDEFINED: i32 = -1;
@@ -213,6 +214,7 @@ impl GameTextInput {
 
 
     pub fn process_event(&self, event_state: ndk_sys::jobject) {
     pub fn process_event(&self, event_state: ndk_sys::jobject) {
         let state = self.state_from_java(event_state);
         let state = self.state_from_java(event_state);
+        t!("IME event: {state:?}");
 
 
         let Some(shared) = &*self.state.lock() else {
         let Some(shared) = &*self.state.lock() else {
             w!("process_event() - no shared state set");
             w!("process_event() - no shared state set");

+ 0 - 4
bin/app/src/android/textinput/mod.rs

@@ -85,8 +85,4 @@ impl AndroidTextInput {
             gti.push_update(&state);
             gti.push_update(&state);
         }
         }
     }
     }
-
-    pub fn get_state(&self) -> AndroidTextInputState {
-        self.state.lock().state.clone()
-    }
 }
 }

+ 34 - 41
bin/app/src/text2/editor/android.rs

@@ -32,6 +32,7 @@ macro_rules! t { ($($arg:tt)*) => { trace!(target: "text::editor::android", $($a
 
 
 pub struct Editor {
 pub struct Editor {
     input: AndroidTextInput,
     input: AndroidTextInput,
+    pub state: AndroidTextInputState,
     pub recvr: async_channel::Receiver<AndroidTextInputState>,
     pub recvr: async_channel::Receiver<AndroidTextInputState>,
 
 
     layout: parley::Layout<Color>,
     layout: parley::Layout<Color>,
@@ -54,9 +55,9 @@ impl Editor {
     ) -> Self {
     ) -> Self {
         let (sender, recvr) = async_channel::unbounded();
         let (sender, recvr) = async_channel::unbounded();
         let input = AndroidTextInput::new(sender);
         let input = AndroidTextInput::new(sender);
-
         Self {
         Self {
             input,
             input,
+            state: Default::default(),
             recvr,
             recvr,
 
 
             layout: Default::default(),
             layout: Default::default(),
@@ -72,11 +73,10 @@ impl Editor {
 
 
     pub async fn on_text_prop_changed(&mut self) {
     pub async fn on_text_prop_changed(&mut self) {
         // Update GameTextInput state
         // Update GameTextInput state
-        let mut state = self.input.get_state().clone();
-        state.text = self.text.get();
-        state.select = (0, 0);
-        state.compose = None;
-        self.input.set_state(state);
+        self.state.text = self.text.get();
+        self.state.select = (0, 0);
+        self.state.compose = None;
+        self.input.set_state(self.state.clone());
         // Refresh our layout
         // Refresh our layout
         self.refresh().await;
         self.refresh().await;
     }
     }
@@ -85,8 +85,7 @@ impl Editor {
         self.refresh().await;
         self.refresh().await;
 
 
         // Update the text attribute
         // Update the text attribute
-        let state = self.input.get_state();
-        self.text.set(atom, &state.text);
+        self.text.set(atom, &self.state.text);
     }
     }
 
 
     pub fn focus(&mut self) {
     pub fn focus(&mut self) {
@@ -102,16 +101,14 @@ impl Editor {
         let window_scale = self.window_scale.get();
         let window_scale = self.window_scale.get();
         let lineheight = self.lineheight.get();
         let lineheight = self.lineheight.get();
 
 
-        let state = self.input.get_state();
-
         let mut underlines = vec![];
         let mut underlines = vec![];
-        if let Some((compose_start, compose_end)) = state.compose {
+        if let Some((compose_start, compose_end)) = self.state.compose {
             underlines.push(compose_start..compose_end);
             underlines.push(compose_start..compose_end);
         }
         }
 
 
         let mut txt_ctx = TEXT_CTX.get().await;
         let mut txt_ctx = TEXT_CTX.get().await;
         self.layout = txt_ctx.make_layout(
         self.layout = txt_ctx.make_layout(
-            &state.text,
+            &self.state.text,
             text_color,
             text_color,
             font_size,
             font_size,
             lineheight,
             lineheight,
@@ -129,11 +126,10 @@ impl Editor {
         let cursor = parley::Cursor::from_point(&self.layout, pos.x, pos.y);
         let cursor = parley::Cursor::from_point(&self.layout, pos.x, pos.y);
         let cursor_idx = cursor.index();
         let cursor_idx = cursor.index();
         t!("  move_to_pos: {cursor_idx}");
         t!("  move_to_pos: {cursor_idx}");
-        let mut state = self.input.get_state().clone();
-        state.text = self.text.get();
-        state.select = (cursor_idx, cursor_idx);
-        state.compose = None;
-        self.input.set_state(state);
+        self.state.text = self.text.get();
+        self.state.select = (cursor_idx, cursor_idx);
+        self.state.compose = None;
+        self.input.set_state(self.state.clone());
     }
     }
 
 
     pub async fn select_word_at_point(&mut self, pos: Point) {
     pub async fn select_word_at_point(&mut self, pos: Point) {
@@ -145,14 +141,12 @@ impl Editor {
 
 
     pub fn get_cursor_pos(&self) -> Point {
     pub fn get_cursor_pos(&self) -> Point {
         let lineheight = self.lineheight.get();
         let lineheight = self.lineheight.get();
-        let state = self.input.get_state();
-
-        let cursor_idx = state.select.0;
+        let cursor_idx = self.state.select.0;
 
 
-        let cursor = if cursor_idx >= state.text.len() {
+        let cursor = if cursor_idx >= self.state.text.len() {
             parley::Cursor::from_byte_index(
             parley::Cursor::from_byte_index(
                 &self.layout,
                 &self.layout,
-                state.text.len(),
+                self.state.text.len(),
                 parley::Affinity::Upstream,
                 parley::Affinity::Upstream,
             )
             )
         } else {
         } else {
@@ -165,11 +159,11 @@ impl Editor {
     pub async fn insert(&mut self, txt: &str, atom: &mut PropertyAtomicGuard) {
     pub async fn insert(&mut self, txt: &str, atom: &mut PropertyAtomicGuard) {
         // TODO: need to verify this is correct
         // TODO: need to verify this is correct
         // Insert text by updating the state
         // Insert text by updating the state
-        let mut current_state = self.input.get_state().clone();
-        current_state.text.push_str(txt);
-        current_state.select = (current_state.text.len(), current_state.text.len());
-        current_state.compose = None;
-        self.input.set_state(current_state);
+        self.state.text.push_str(txt);
+        let cursor_idx = self.state.text.len();
+        self.state.select = (cursor_idx, cursor_idx);
+        self.state.compose = None;
+        self.input.set_state(self.state.clone());
         self.on_buffer_changed(atom).await;
         self.on_buffer_changed(atom).await;
     }
     }
 
 
@@ -191,21 +185,21 @@ impl Editor {
     }
     }
 
 
     pub fn selected_text(&self) -> Option<String> {
     pub fn selected_text(&self) -> Option<String> {
-        let state = self.input.get_state();
-        if state.select.0 == state.select.1 {
+        let (start, end) = (self.state.select.0, self.state.select.1);
+        if start == end {
             return None
             return None
         }
         }
-        let (start, end) =
-            (min(state.select.0, state.select.1), max(state.select.0, state.select.1));
-        Some(state.text[start..end].to_string())
+        let (start, end) = (min(start, end), max(start, end));
+        Some(self.state.text[start..end].to_string())
     }
     }
     pub fn selection(&self, side: isize) -> parley::Selection {
     pub fn selection(&self, side: isize) -> parley::Selection {
         assert!(side.abs() == 1);
         assert!(side.abs() == 1);
-        let state = self.input.get_state();
+        t!("selection({side}) [state={:?}]", self.state);
 
 
+        let (start, end) = (self.state.select.0, self.state.select.1);
         let (anchor, focus) = match side {
         let (anchor, focus) = match side {
-            -1 => (state.select.1, state.select.0),
-            1 => (state.select.0, state.select.1),
+            -1 => (end, start),
+            1 => (start, end),
             _ => panic!(),
             _ => panic!(),
         };
         };
 
 
@@ -217,15 +211,14 @@ impl Editor {
         parley::Selection::new(anchor, focus)
         parley::Selection::new(anchor, focus)
     }
     }
     pub async fn set_selection(&mut self, select_start: usize, select_end: usize) {
     pub async fn set_selection(&mut self, select_start: usize, select_end: usize) {
-        let mut state = self.input.get_state().clone();
-        state.text = self.text.get();
-        state.select = (select_start, select_end);
-        state.compose = None;
-        self.input.set_state(state);
+        self.state.text = self.text.get();
+        self.state.select = (select_start, select_end);
+        self.state.compose = None;
+        self.input.set_state(self.state.clone());
     }
     }
 
 
     #[allow(dead_code)]
     #[allow(dead_code)]
     pub fn buffer(&self) -> String {
     pub fn buffer(&self) -> String {
-        self.input.get_state().text.clone()
+        self.state.text.clone()
     }
     }
 }
 }

+ 5 - 2
bin/app/src/ui/edit/mod.rs

@@ -1300,16 +1300,19 @@ impl BaseEdit {
         if !self.is_active.get() {
         if !self.is_active.get() {
             return
             return
         }
         }
-        t!("handle_android_event({state:?})");
 
 
+        t!("handle_android_event({state:?})");
         let atom = &mut self.render_api.make_guard(gfxtag!("BaseEdit::handle_android_event"));
         let atom = &mut self.render_api.make_guard(gfxtag!("BaseEdit::handle_android_event"));
 
 
         // Text changed - finish any active selection
         // Text changed - finish any active selection
-        if state.text != self.text.get() {
+        if state.text != self.text.get() || state.select.0 == state.select.1 {
+            // Safe to call before we update the editor.
+            // I just wanna avoid cloning state since we move it into editor.
             self.finish_select(atom);
             self.finish_select(atom);
         }
         }
 
 
         let mut editor = self.lock_editor().await;
         let mut editor = self.lock_editor().await;
+        editor.state = state;
         editor.on_buffer_changed(atom).await;
         editor.on_buffer_changed(atom).await;
         drop(editor);
         drop(editor);