diff options
| author | 2025-02-02 20:45:29 +0100 | |
|---|---|---|
| committer | 2025-02-02 20:45:29 +0100 | |
| commit | ae10adda74320e8098bfeb401f12a278e1e7b3e2 (patch) | |
| tree | 1827aabad023b06a6cb9dd6ec50093af969ecf0c /widget | |
| parent | d5ee9c27955e6dfeb645e2641f3d24b006685484 (diff) | |
| download | iced-ae10adda74320e8098bfeb401f12a278e1e7b3e2.tar.gz iced-ae10adda74320e8098bfeb401f12a278e1e7b3e2.tar.bz2 iced-ae10adda74320e8098bfeb401f12a278e1e7b3e2.zip | |
Refactor and simplify `input_method` API
Diffstat (limited to '')
| -rw-r--r-- | widget/src/action.rs | 14 | ||||
| -rw-r--r-- | widget/src/canvas.rs | 15 | ||||
| -rw-r--r-- | widget/src/combo_box.rs | 16 | ||||
| -rw-r--r-- | widget/src/lazy/component.rs | 29 | ||||
| -rw-r--r-- | widget/src/pane_grid.rs | 2 | ||||
| -rw-r--r-- | widget/src/scrollable.rs | 18 | ||||
| -rw-r--r-- | widget/src/shader.rs | 14 | ||||
| -rw-r--r-- | widget/src/text_editor.rs | 133 | ||||
| -rw-r--r-- | widget/src/text_input.rs | 148 | 
9 files changed, 186 insertions, 203 deletions
| diff --git a/widget/src/action.rs b/widget/src/action.rs index 1dd3a787..cc31e76a 100644 --- a/widget/src/action.rs +++ b/widget/src/action.rs @@ -6,7 +6,7 @@ use crate::core::window;  #[derive(Debug, Clone)]  pub struct Action<Message> {      message_to_publish: Option<Message>, -    redraw_request: Option<window::RedrawRequest>, +    redraw_request: window::RedrawRequest,      event_status: event::Status,  } @@ -14,7 +14,7 @@ impl<Message> Action<Message> {      fn new() -> Self {          Self {              message_to_publish: None, -            redraw_request: None, +            redraw_request: window::RedrawRequest::Wait,              event_status: event::Status::Ignored,          }      } @@ -46,7 +46,7 @@ impl<Message> Action<Message> {      /// soon as possible; without publishing any `Message`.      pub fn request_redraw() -> Self {          Self { -            redraw_request: Some(window::RedrawRequest::NextFrame), +            redraw_request: window::RedrawRequest::NextFrame,              ..Self::new()          }      } @@ -58,7 +58,7 @@ impl<Message> Action<Message> {      /// blinking caret on a text input.      pub fn request_redraw_at(at: Instant) -> Self {          Self { -            redraw_request: Some(window::RedrawRequest::At(at)), +            redraw_request: window::RedrawRequest::At(at),              ..Self::new()          }      } @@ -75,11 +75,7 @@ impl<Message> Action<Message> {      /// widget implementations.      pub fn into_inner(          self, -    ) -> ( -        Option<Message>, -        Option<window::RedrawRequest>, -        event::Status, -    ) { +    ) -> (Option<Message>, window::RedrawRequest, event::Status) {          (              self.message_to_publish,              self.redraw_request, diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 23cc3f2b..d10771f0 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -238,27 +238,18 @@ where          {              let (message, redraw_request, event_status) = action.into_inner(); +            shell.request_redraw_at(redraw_request); +              if let Some(message) = message {                  shell.publish(message);              } -            if let Some(redraw_request) = redraw_request { -                match redraw_request { -                    window::RedrawRequest::NextFrame => { -                        shell.request_redraw(); -                    } -                    window::RedrawRequest::At(at) => { -                        shell.request_redraw_at(at); -                    } -                } -            } -              if event_status == event::Status::Captured {                  shell.capture_event();              }          } -        if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { +        if shell.redraw_request() != window::RedrawRequest::NextFrame {              let mouse_interaction = self                  .mouse_interaction(tree, layout, cursor, viewport, renderer); diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index d7c7c922..05793155 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -63,7 +63,6 @@ use crate::core::renderer;  use crate::core::text;  use crate::core::time::Instant;  use crate::core::widget::{self, Widget}; -use crate::core::window;  use crate::core::{      Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme,      Vector, @@ -554,17 +553,8 @@ where              shell.capture_event();          } -        if let Some(redraw_request) = local_shell.redraw_request() { -            match redraw_request { -                window::RedrawRequest::NextFrame => { -                    shell.request_redraw(); -                } -                window::RedrawRequest::At(at) => { -                    shell.request_redraw_at(at); -                } -            } -        } -        shell.update_caret_info(local_shell.caret_info()); +        shell.request_redraw_at(local_shell.redraw_request()); +        shell.request_input_method(local_shell.input_method());          // Then finally react to them here          for message in local_messages { @@ -757,7 +747,7 @@ where                      &mut local_shell,                      viewport,                  ); -                shell.update_caret_info(local_shell.caret_info()); +                shell.request_input_method(local_shell.input_method());              }          }); diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index b9fbde58..c93b7c42 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -6,7 +6,6 @@ use crate::core::overlay;  use crate::core::renderer;  use crate::core::widget;  use crate::core::widget::tree::{self, Tree}; -use crate::core::window;  use crate::core::{      self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,      Widget, @@ -344,18 +343,8 @@ where          }          local_shell.revalidate_layout(|| shell.invalidate_layout()); - -        if let Some(redraw_request) = local_shell.redraw_request() { -            match redraw_request { -                window::RedrawRequest::NextFrame => { -                    shell.request_redraw(); -                } -                window::RedrawRequest::At(at) => { -                    shell.request_redraw_at(at); -                } -            } -        } -        shell.update_caret_info(local_shell.caret_info()); +        shell.request_redraw_at(local_shell.redraw_request()); +        shell.request_input_method(local_shell.input_method());          if !local_messages.is_empty() {              let mut heads = self.state.take().unwrap().into_heads(); @@ -630,18 +619,8 @@ where          }          local_shell.revalidate_layout(|| shell.invalidate_layout()); - -        if let Some(redraw_request) = local_shell.redraw_request() { -            match redraw_request { -                window::RedrawRequest::NextFrame => { -                    shell.request_redraw(); -                } -                window::RedrawRequest::At(at) => { -                    shell.request_redraw_at(at); -                } -            } -        } -        shell.update_caret_info(local_shell.caret_info()); +        shell.request_redraw_at(local_shell.redraw_request()); +        shell.request_input_method(local_shell.input_method());          if !local_messages.is_empty() {              let mut inner = diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 5c3b343c..e972b983 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -687,7 +687,7 @@ where              _ => {}          } -        if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { +        if shell.redraw_request() != window::RedrawRequest::NextFrame {              let interaction = self                  .grid_interaction(action, layout, cursor)                  .or_else(|| { diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 7df7a0e5..0a93584e 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -33,7 +33,7 @@ use crate::core::widget::operation::{self, Operation};  use crate::core::widget::tree::{self, Tree};  use crate::core::window;  use crate::core::{ -    self, Background, CaretInfo, Clipboard, Color, Element, Event, Layout, +    self, Background, Clipboard, Color, Element, Event, InputMethod, Layout,      Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector,      Widget,  }; @@ -730,7 +730,6 @@ where                  let translation =                      state.translation(self.direction, bounds, content_bounds); -                let children_may_have_caret = shell.caret_info().is_none();                  self.content.as_widget_mut().update(                      &mut tree.children[0],                      event.clone(), @@ -746,17 +745,10 @@ where                      },                  ); -                if children_may_have_caret { -                    if let Some(caret_info) = shell.caret_info() { -                        shell.update_caret_info(Some(CaretInfo { -                            position: Point::new( -                                caret_info.position.x - translation.x, -                                caret_info.position.y - translation.y, -                            ), -                            input_method_allowed: caret_info -                                .input_method_allowed, -                        })); -                    } +                if let InputMethod::Open { position, .. } = +                    shell.input_method_mut() +                { +                    *position = *position + translation;                  }              }; diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 8ec57482..48c96321 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -9,7 +9,6 @@ use crate::core::mouse;  use crate::core::renderer;  use crate::core::widget::tree::{self, Tree};  use crate::core::widget::{self, Widget}; -use crate::core::window;  use crate::core::{Clipboard, Element, Event, Length, Rectangle, Shell, Size};  use crate::renderer::wgpu::primitive; @@ -105,21 +104,12 @@ where          {              let (message, redraw_request, event_status) = action.into_inner(); +            shell.request_redraw_at(redraw_request); +              if let Some(message) = message {                  shell.publish(message);              } -            if let Some(redraw_request) = redraw_request { -                match redraw_request { -                    window::RedrawRequest::NextFrame => { -                        shell.request_redraw(); -                    } -                    window::RedrawRequest::At(at) => { -                        shell.request_redraw_at(at); -                    } -                } -            } -              if event_status == event::Status::Captured {                  shell.capture_event();              } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 529c8b90..4f985f28 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -47,7 +47,7 @@ use crate::core::widget::operation;  use crate::core::widget::{self, Widget};  use crate::core::window;  use crate::core::{ -    Background, Border, CaretInfo, Color, Element, Event, Length, Padding, +    Background, Border, Color, Element, Event, InputMethod, Length, Padding,      Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector,  }; @@ -324,43 +324,49 @@ where          self      } -    fn caret_rect( +    fn input_method<'b>(          &self, -        tree: &widget::Tree, +        state: &'b State<Highlighter>,          renderer: &Renderer,          layout: Layout<'_>, -    ) -> Option<Rectangle> { -        let bounds = layout.bounds(); +    ) -> InputMethod<&'b str> { +        let Some(Focus { +            is_window_focused: true, +            is_ime_open, +            .. +        }) = &state.focus +        else { +            return InputMethod::Disabled; +        }; + +        let Some(preedit) = &is_ime_open else { +            return InputMethod::Allowed; +        }; +        let bounds = layout.bounds();          let internal = self.content.0.borrow_mut(); -        let state = tree.state.downcast_ref::<State<Highlighter>>();          let text_bounds = bounds.shrink(self.padding);          let translation = text_bounds.position() - Point::ORIGIN; -        if state.focus.is_some() { -            let position = match internal.editor.cursor() { -                Cursor::Caret(position) => position, -                Cursor::Selection(ranges) => ranges -                    .first() -                    .cloned() -                    .unwrap_or(Rectangle::default()) -                    .position(), -            }; -            Some(Rectangle::new( -                position + translation, -                Size::new( -                    1.0, -                    self.line_height -                        .to_absolute( -                            self.text_size -                                .unwrap_or_else(|| renderer.default_size()), -                        ) -                        .into(), -                ), -            )) -        } else { -            None +        let cursor = match internal.editor.cursor() { +            Cursor::Caret(position) => position, +            Cursor::Selection(ranges) => { +                ranges.first().cloned().unwrap_or_default().position() +            } +        }; + +        let line_height = self.line_height.to_absolute( +            self.text_size.unwrap_or_else(|| renderer.default_size()), +        ); + +        let position = +            cursor + translation + Vector::new(0.0, f32::from(line_height)); + +        InputMethod::Open { +            position, +            purpose: input_method::Purpose::Normal, +            preedit: Some(preedit),          }      }  } @@ -499,11 +505,12 @@ pub struct State<Highlighter: text::Highlighter> {      highlighter_format_address: usize,  } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)]  struct Focus {      updated_at: Instant,      now: Instant,      is_window_focused: bool, +    is_ime_open: Option<String>,  }  impl Focus { @@ -516,6 +523,7 @@ impl Focus {              updated_at: now,              now,              is_window_focused: true, +            is_ime_open: None,          }      } @@ -742,11 +750,23 @@ where                      }));                      shell.capture_event();                  } -                Update::Commit(text) => { -                    shell.publish(on_edit(Action::Edit(Edit::Paste( -                        Arc::new(text), -                    )))); -                } +                Update::InputMethod(update) => match update { +                    Ime::Toggle(is_open) => { +                        if let Some(focus) = &mut state.focus { +                            focus.is_ime_open = is_open.then(String::new); +                        } +                    } +                    Ime::Preedit(text) => { +                        if let Some(focus) = &mut state.focus { +                            focus.is_ime_open = Some(text); +                        } +                    } +                    Ime::Commit(text) => { +                        shell.publish(on_edit(Action::Edit(Edit::Paste( +                            Arc::new(text), +                        )))); +                    } +                },                  Update::Binding(binding) => {                      fn apply_binding<                          H: text::Highlighter, @@ -871,22 +891,12 @@ where              }          }; -        shell.update_caret_info(if state.is_focused() { -            let rect = -                self.caret_rect(tree, renderer, layout).unwrap_or_default(); - -            let bottom_left = Point::new(rect.x, rect.y + rect.height); - -            Some(CaretInfo { -                position: bottom_left, -                input_method_allowed: true, -            }) -        } else { -            None -        }); -          if is_redraw {              self.last_status = Some(status); + +            shell.request_input_method( +                &self.input_method(state, renderer, layout), +            );          } else if self              .last_status              .is_some_and(|last_status| status != last_status) @@ -1189,10 +1199,16 @@ enum Update<Message> {      Drag(Point),      Release,      Scroll(f32), -    Commit(String), +    InputMethod(Ime),      Binding(Binding<Message>),  } +enum Ime { +    Toggle(bool), +    Preedit(String), +    Commit(String), +} +  impl<Message> Update<Message> {      fn from_event<H: Highlighter>(          event: Event, @@ -1252,9 +1268,20 @@ impl<Message> Update<Message> {                  }                  _ => None,              }, -            Event::InputMethod(input_method::Event::Commit(text)) => { -                Some(Update::Commit(text)) -            } +            Event::InputMethod(event) => match event { +                input_method::Event::Opened | input_method::Event::Closed => { +                    Some(Update::InputMethod(Ime::Toggle(matches!( +                        event, +                        input_method::Event::Opened +                    )))) +                } +                input_method::Event::Preedit(content, _range) => { +                    Some(Update::InputMethod(Ime::Preedit(content))) +                } +                input_method::Event::Commit(content) => { +                    Some(Update::InputMethod(Ime::Commit(content))) +                } +            },              Event::Keyboard(keyboard::Event::KeyPressed {                  key,                  modifiers, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ba5d1843..d0e93927 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -57,7 +57,7 @@ use crate::core::widget::operation::{self, Operation};  use crate::core::widget::tree::{self, Tree};  use crate::core::window;  use crate::core::{ -    Background, Border, CaretInfo, Color, Element, Event, Layout, Length, +    Background, Border, Color, Element, Event, InputMethod, Layout, Length,      Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,  };  use crate::runtime::task::{self, Task}; @@ -392,14 +392,24 @@ where          }      } -    fn caret_rect( +    fn input_method<'b>(          &self, -        tree: &Tree, +        state: &'b State<Renderer::Paragraph>,          layout: Layout<'_>, -        value: Option<&Value>, -    ) -> Option<Rectangle> { -        let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>(); -        let value = value.unwrap_or(&self.value); +        value: &Value, +    ) -> InputMethod<&'b str> { +        let Some(Focus { +            is_window_focused: true, +            is_ime_open, +            .. +        }) = &state.is_focused +        else { +            return InputMethod::Disabled; +        }; + +        let Some(preedit) = is_ime_open else { +            return InputMethod::Allowed; +        };          let secure_value = self.is_secure.then(|| value.secure());          let value = secure_value.as_ref().unwrap_or(value); @@ -407,38 +417,32 @@ where          let mut children_layout = layout.children();          let text_bounds = children_layout.next().unwrap().bounds(); -        if state -            .is_focused -            .is_some_and(|focus| focus.is_window_focused) -        { -            let caret_index = match state.cursor.state(value) { -                cursor::State::Index(position) => position, -                cursor::State::Selection { start, end } => start.min(end), -            }; +        let caret_index = match state.cursor.state(value) { +            cursor::State::Index(position) => position, +            cursor::State::Selection { start, end } => start.min(end), +        }; -            let text = state.value.raw(); -            let (caret_x, offset) = measure_cursor_and_scroll_offset( -                text, -                text_bounds, -                caret_index, -            ); +        let text = state.value.raw(); +        let (cursor_x, scroll_offset) = +            measure_cursor_and_scroll_offset(text, text_bounds, caret_index); -            let alignment_offset = alignment_offset( -                text_bounds.width, -                text.min_width(), -                self.alignment, -            ); +        let alignment_offset = alignment_offset( +            text_bounds.width, +            text.min_width(), +            self.alignment, +        ); -            let x = (text_bounds.x + caret_x).floor(); +        let x = (text_bounds.x + cursor_x).floor() - scroll_offset +            + alignment_offset; -            Some(Rectangle { -                x: (alignment_offset - offset) + x, -                y: text_bounds.y, -                width: 1.0, -                height: text_bounds.height, -            }) -        } else { -            None +        InputMethod::Open { +            position: Point::new(x, text_bounds.y), +            purpose: if self.is_secure { +                input_method::Purpose::Secure +            } else { +                input_method::Purpose::Normal +            }, +            preedit: Some(preedit),          }      } @@ -725,6 +729,7 @@ where                          updated_at: now,                          now,                          is_window_focused: true, +                        is_ime_open: None,                      })                  } else {                      None @@ -1248,28 +1253,46 @@ where                  state.keyboard_modifiers = *modifiers;              } -            Event::InputMethod(input_method::Event::Commit(text)) => { -                let state = state::<Renderer>(tree); +            Event::InputMethod(event) => match event { +                input_method::Event::Opened | input_method::Event::Closed => { +                    let state = state::<Renderer>(tree); -                if let Some(focus) = &mut state.is_focused { -                    let Some(on_input) = &self.on_input else { -                        return; -                    }; +                    if let Some(focus) = &mut state.is_focused { +                        focus.is_ime_open = +                            matches!(event, input_method::Event::Opened) +                                .then(String::new); +                    } +                } +                input_method::Event::Preedit(content, _range) => { +                    let state = state::<Renderer>(tree); -                    let mut editor = -                        Editor::new(&mut self.value, &mut state.cursor); -                    editor.paste(Value::new(text)); +                    if let Some(focus) = &mut state.is_focused { +                        focus.is_ime_open = Some(content.to_owned()); +                    } +                } +                input_method::Event::Commit(text) => { +                    let state = state::<Renderer>(tree); -                    focus.updated_at = Instant::now(); -                    state.is_pasting = None; +                    if let Some(focus) = &mut state.is_focused { +                        let Some(on_input) = &self.on_input else { +                            return; +                        }; -                    let message = (on_input)(editor.contents()); -                    shell.publish(message); -                    shell.capture_event(); +                        let mut editor = +                            Editor::new(&mut self.value, &mut state.cursor); +                        editor.paste(Value::new(text)); + +                        focus.updated_at = Instant::now(); +                        state.is_pasting = None; -                    update_cache(state, &self.value); +                        let message = (on_input)(editor.contents()); +                        shell.publish(message); +                        shell.capture_event(); + +                        update_cache(state, &self.value); +                    }                  } -            } +            },              Event::Window(window::Event::Unfocused) => {                  let state = state::<Renderer>(tree); @@ -1329,21 +1352,14 @@ where              Status::Active          }; -        shell.update_caret_info(if state.is_focused() { -            let rect = self -                .caret_rect(tree, layout, Some(&self.value)) -                .unwrap_or(Rectangle::with_size(Size::<f32>::default())); -            let bottom_left = Point::new(rect.x, rect.y + rect.height); -            Some(CaretInfo { -                position: bottom_left, -                input_method_allowed: true, -            }) -        } else { -            None -        }); -          if let Event::Window(window::Event::RedrawRequested(_now)) = event {              self.last_status = Some(status); + +            shell.request_input_method(&self.input_method( +                state, +                layout, +                &self.value, +            ));          } else if self              .last_status              .is_some_and(|last_status| status != last_status) @@ -1517,11 +1533,12 @@ fn state<Renderer: text::Renderer>(      tree.state.downcast_mut::<State<Renderer::Paragraph>>()  } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)]  struct Focus {      updated_at: Instant,      now: Instant,      is_window_focused: bool, +    is_ime_open: Option<String>,  }  impl<P: text::Paragraph> State<P> { @@ -1548,6 +1565,7 @@ impl<P: text::Paragraph> State<P> {              updated_at: now,              now,              is_window_focused: true, +            is_ime_open: None,          });          self.move_cursor_to_end(); | 
