diff options
Diffstat (limited to '')
| -rw-r--r-- | widget/src/text_input.rs | 432 | ||||
| -rw-r--r-- | widget/src/text_input/cursor.rs | 26 | ||||
| -rw-r--r-- | widget/src/text_input/value.rs | 13 | 
3 files changed, 270 insertions, 201 deletions
| diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 03bcb86a..f1688746 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -17,7 +17,7 @@ use crate::core::keyboard;  use crate::core::layout;  use crate::core::mouse::{self, click};  use crate::core::renderer; -use crate::core::text::{self, Text}; +use crate::core::text::{self, Paragraph as _, Text};  use crate::core::time::{Duration, Instant};  use crate::core::touch;  use crate::core::widget; @@ -67,7 +67,7 @@ where      font: Option<Renderer::Font>,      width: Length,      padding: Padding, -    size: Option<f32>, +    size: Option<Pixels>,      line_height: text::LineHeight,      on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,      on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>, @@ -76,6 +76,9 @@ where      style: <Renderer::Theme as StyleSheet>::Style,  } +/// The default [`Padding`] of a [`TextInput`]. +pub const DEFAULT_PADDING: Padding = Padding::new(5.0); +  impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>  where      Message: Clone, @@ -95,7 +98,7 @@ where              is_secure: false,              font: None,              width: Length::Fill, -            padding: Padding::new(5.0), +            padding: DEFAULT_PADDING,              size: None,              line_height: text::LineHeight::default(),              on_input: None, @@ -175,11 +178,11 @@ where      /// Sets the text size of the [`TextInput`].      pub fn size(mut self, size: impl Into<Pixels>) -> Self { -        self.size = Some(size.into().0); +        self.size = Some(size.into());          self      } -    /// Sets the [`LineHeight`] of the [`TextInput`]. +    /// Sets the [`text::LineHeight`] of the [`TextInput`].      pub fn line_height(          mut self,          line_height: impl Into<text::LineHeight>, @@ -197,6 +200,32 @@ where          self      } +    /// Lays out the [`TextInput`], overriding its [`Value`] if provided. +    /// +    /// [`Renderer`]: text::Renderer +    pub fn layout( +        &self, +        tree: &mut Tree, +        renderer: &Renderer, +        limits: &layout::Limits, +        value: Option<&Value>, +    ) -> layout::Node { +        layout( +            renderer, +            limits, +            self.width, +            self.padding, +            self.size, +            self.font, +            self.line_height, +            self.icon.as_ref(), +            tree.state.downcast_mut::<State<Renderer::Paragraph>>(), +            value.unwrap_or(&self.value), +            &self.placeholder, +            self.is_secure, +        ) +    } +      /// Draws the [`TextInput`] with the given [`Renderer`], overriding its      /// [`Value`] if provided.      /// @@ -215,17 +244,13 @@ where              theme,              layout,              cursor, -            tree.state.downcast_ref::<State>(), +            tree.state.downcast_ref::<State<Renderer::Paragraph>>(),              value.unwrap_or(&self.value), -            &self.placeholder, -            self.size, -            self.line_height, -            self.font,              self.on_input.is_none(),              self.is_secure,              self.icon.as_ref(),              &self.style, -        ) +        );      }  } @@ -237,15 +262,15 @@ where      Renderer::Theme: StyleSheet,  {      fn tag(&self) -> tree::Tag { -        tree::Tag::of::<State>() +        tree::Tag::of::<State<Renderer::Paragraph>>()      }      fn state(&self) -> tree::State { -        tree::State::new(State::new()) +        tree::State::new(State::<Renderer::Paragraph>::new())      }      fn diff(&self, tree: &mut Tree) { -        let state = tree.state.downcast_mut::<State>(); +        let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();          // Unfocus text input if it becomes disabled          if self.on_input.is_none() { @@ -266,6 +291,7 @@ where      fn layout(          &self, +        tree: &mut Tree,          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { @@ -275,8 +301,13 @@ where              self.width,              self.padding,              self.size, +            self.font,              self.line_height,              self.icon.as_ref(), +            tree.state.downcast_mut::<State<Renderer::Paragraph>>(), +            &self.value, +            &self.placeholder, +            self.is_secure,          )      } @@ -287,7 +318,7 @@ where          _renderer: &Renderer,          operation: &mut dyn Operation<Message>,      ) { -        let state = tree.state.downcast_mut::<State>(); +        let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();          operation.focusable(state, self.id.as_ref().map(|id| &id.0));          operation.text_input(state, self.id.as_ref().map(|id| &id.0)); @@ -302,6 +333,7 @@ where          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>, +        _viewport: &Rectangle,      ) -> event::Status {          update(              event, @@ -318,7 +350,7 @@ where              self.on_input.as_deref(),              self.on_paste.as_deref(),              &self.on_submit, -            || tree.state.downcast_mut::<State>(), +            || tree.state.downcast_mut::<State<Renderer::Paragraph>>(),          )      } @@ -337,17 +369,13 @@ where              theme,              layout,              cursor, -            tree.state.downcast_ref::<State>(), +            tree.state.downcast_ref::<State<Renderer::Paragraph>>(),              &self.value, -            &self.placeholder, -            self.size, -            self.line_height, -            self.font,              self.on_input.is_none(),              self.is_secure,              self.icon.as_ref(),              &self.style, -        ) +        );      }      fn mouse_interaction( @@ -384,7 +412,7 @@ pub struct Icon<Font> {      /// The unicode code point that will be used as the icon.      pub code_point: char,      /// The font size of the content. -    pub size: Option<f32>, +    pub size: Option<Pixels>,      /// The spacing between the [`Icon`] and the text in a [`TextInput`].      pub spacing: f32,      /// The side of a [`TextInput`] where to display the [`Icon`]. @@ -461,29 +489,65 @@ pub fn layout<Renderer>(      limits: &layout::Limits,      width: Length,      padding: Padding, -    size: Option<f32>, +    size: Option<Pixels>, +    font: Option<Renderer::Font>,      line_height: text::LineHeight,      icon: Option<&Icon<Renderer::Font>>, +    state: &mut State<Renderer::Paragraph>, +    value: &Value, +    placeholder: &str, +    is_secure: bool,  ) -> layout::Node  where      Renderer: text::Renderer,  { +    let font = font.unwrap_or_else(|| renderer.default_font());      let text_size = size.unwrap_or_else(|| renderer.default_size()); +      let padding = padding.fit(Size::ZERO, limits.max());      let limits = limits          .width(width)          .pad(padding) -        .height(line_height.to_absolute(Pixels(text_size))); +        .height(line_height.to_absolute(text_size));      let text_bounds = limits.resolve(Size::ZERO); +    let placeholder_text = Text { +        font, +        line_height, +        content: placeholder, +        bounds: Size::new(f32::INFINITY, text_bounds.height), +        size: text_size, +        horizontal_alignment: alignment::Horizontal::Left, +        vertical_alignment: alignment::Vertical::Center, +        shaping: text::Shaping::Advanced, +    }; + +    state.placeholder.update(placeholder_text); + +    let secure_value = is_secure.then(|| value.secure()); +    let value = secure_value.as_ref().unwrap_or(value); + +    state.value.update(Text { +        content: &value.to_string(), +        ..placeholder_text +    }); +      if let Some(icon) = icon { -        let icon_width = renderer.measure_width( -            &icon.code_point.to_string(), -            icon.size.unwrap_or_else(|| renderer.default_size()), -            icon.font, -            text::Shaping::Advanced, -        ); +        let icon_text = Text { +            line_height, +            content: &icon.code_point.to_string(), +            font: icon.font, +            size: icon.size.unwrap_or_else(|| renderer.default_size()), +            bounds: Size::new(f32::INFINITY, text_bounds.height), +            horizontal_alignment: alignment::Horizontal::Center, +            vertical_alignment: alignment::Vertical::Center, +            shaping: text::Shaping::Advanced, +        }; + +        state.icon.update(icon_text); + +        let icon_width = state.icon.min_width();          let mut text_node = layout::Node::new(              text_bounds - Size::new(icon_width + icon.spacing, 0.0), @@ -533,19 +597,31 @@ pub fn update<'a, Message, Renderer>(      clipboard: &mut dyn Clipboard,      shell: &mut Shell<'_, Message>,      value: &mut Value, -    size: Option<f32>, +    size: Option<Pixels>,      line_height: text::LineHeight,      font: Option<Renderer::Font>,      is_secure: bool,      on_input: Option<&dyn Fn(String) -> Message>,      on_paste: Option<&dyn Fn(String) -> Message>,      on_submit: &Option<Message>, -    state: impl FnOnce() -> &'a mut State, +    state: impl FnOnce() -> &'a mut State<Renderer::Paragraph>,  ) -> event::Status  where      Message: Clone,      Renderer: text::Renderer,  { +    let update_cache = |state, value| { +        replace_paragraph( +            renderer, +            state, +            layout, +            value, +            font, +            size, +            line_height, +        ); +    }; +      match event {          Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))          | Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -564,6 +640,7 @@ where                      Some(Focus {                          updated_at: now,                          now, +                        is_window_focused: true,                      })                  })              } else { @@ -587,11 +664,7 @@ where                              };                              find_cursor_position( -                                renderer,                                  text_layout.bounds(), -                                font, -                                size, -                                line_height,                                  &value,                                  state,                                  target, @@ -616,11 +689,7 @@ where                              state.cursor.select_all(value);                          } else {                              let position = find_cursor_position( -                                renderer,                                  text_layout.bounds(), -                                font, -                                size, -                                line_height,                                  value,                                  state,                                  target, @@ -666,11 +735,7 @@ where                  };                  let position = find_cursor_position( -                    renderer,                      text_layout.bounds(), -                    font, -                    size, -                    line_height,                      &value,                      state,                      target, @@ -688,7 +753,9 @@ where              let state = state();              if let Some(focus) = &mut state.is_focused { -                let Some(on_input) = on_input else { return event::Status::Ignored }; +                let Some(on_input) = on_input else { +                    return event::Status::Ignored; +                };                  if state.is_pasting.is_none()                      && !state.keyboard_modifiers.command() @@ -703,6 +770,8 @@ where                      focus.updated_at = Instant::now(); +                    update_cache(state, value); +                      return event::Status::Captured;                  }              } @@ -711,7 +780,9 @@ where              let state = state();              if let Some(focus) = &mut state.is_focused { -                let Some(on_input) = on_input else { return event::Status::Ignored }; +                let Some(on_input) = on_input else { +                    return event::Status::Ignored; +                };                  let modifiers = state.keyboard_modifiers;                  focus.updated_at = Instant::now(); @@ -740,6 +811,8 @@ where                          let message = (on_input)(editor.contents());                          shell.publish(message); + +                        update_cache(state, value);                      }                      keyboard::KeyCode::Delete => {                          if platform::is_jump_modifier_pressed(modifiers) @@ -760,6 +833,8 @@ where                          let message = (on_input)(editor.contents());                          shell.publish(message); + +                        update_cache(state, value);                      }                      keyboard::KeyCode::Left => {                          if platform::is_jump_modifier_pressed(modifiers) @@ -771,7 +846,7 @@ where                                  state.cursor.move_left_by_words(value);                              }                          } else if modifiers.shift() { -                            state.cursor.select_left(value) +                            state.cursor.select_left(value);                          } else {                              state.cursor.move_left(value);                          } @@ -786,7 +861,7 @@ where                                  state.cursor.move_right_by_words(value);                              }                          } else if modifiers.shift() { -                            state.cursor.select_right(value) +                            state.cursor.select_right(value);                          } else {                              state.cursor.move_right(value);                          } @@ -835,9 +910,13 @@ where                          let message = (on_input)(editor.contents());                          shell.publish(message); + +                        update_cache(state, value);                      }                      keyboard::KeyCode::V => { -                        if state.keyboard_modifiers.command() { +                        if state.keyboard_modifiers.command() +                            && !state.keyboard_modifiers.alt() +                        {                              let content = match state.is_pasting.take() {                                  Some(content) => content,                                  None => { @@ -865,6 +944,8 @@ where                              shell.publish(message);                              state.is_pasting = Some(content); + +                            update_cache(state, value);                          } else {                              state.is_pasting = None;                          } @@ -919,19 +1000,38 @@ where              state.keyboard_modifiers = modifiers;          } +        Event::Window(_, window::Event::Unfocused) => { +            let state = state(); + +            if let Some(focus) = &mut state.is_focused { +                focus.is_window_focused = false; +            } +        } +        Event::Window(_, window::Event::Focused) => { +            let state = state(); + +            if let Some(focus) = &mut state.is_focused { +                focus.is_window_focused = true; +                focus.updated_at = Instant::now(); + +                shell.request_redraw(window::RedrawRequest::NextFrame); +            } +        }          Event::Window(_, window::Event::RedrawRequested(now)) => {              let state = state();              if let Some(focus) = &mut state.is_focused { -                focus.now = now; +                if focus.is_window_focused { +                    focus.now = now; -                let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS -                    - (now - focus.updated_at).as_millis() -                        % CURSOR_BLINK_INTERVAL_MILLIS; +                    let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS +                        - (now - focus.updated_at).as_millis() +                            % CURSOR_BLINK_INTERVAL_MILLIS; -                shell.request_redraw(window::RedrawRequest::At( -                    now + Duration::from_millis(millis_until_redraw as u64), -                )); +                    shell.request_redraw(window::RedrawRequest::At( +                        now + Duration::from_millis(millis_until_redraw as u64), +                    )); +                }              }          }          _ => {} @@ -949,12 +1049,8 @@ pub fn draw<Renderer>(      theme: &Renderer::Theme,      layout: Layout<'_>,      cursor: mouse::Cursor, -    state: &State, +    state: &State<Renderer::Paragraph>,      value: &Value, -    placeholder: &str, -    size: Option<f32>, -    line_height: text::LineHeight, -    font: Option<Renderer::Font>,      is_disabled: bool,      is_secure: bool,      icon: Option<&Icon<Renderer::Font>>, @@ -993,40 +1089,30 @@ pub fn draw<Renderer>(          appearance.background,      ); -    if let Some(icon) = icon { +    if icon.is_some() {          let icon_layout = children_layout.next().unwrap(); -        renderer.fill_text(Text { -            content: &icon.code_point.to_string(), -            size: icon.size.unwrap_or_else(|| renderer.default_size()), -            line_height: text::LineHeight::default(), -            font: icon.font, -            color: appearance.icon_color, -            bounds: Rectangle { -                y: text_bounds.center_y(), -                ..icon_layout.bounds() -            }, -            horizontal_alignment: alignment::Horizontal::Left, -            vertical_alignment: alignment::Vertical::Center, -            shaping: text::Shaping::Advanced, -        }); +        renderer.fill_paragraph( +            &state.icon, +            icon_layout.bounds().center(), +            appearance.icon_color, +        );      }      let text = value.to_string(); -    let font = font.unwrap_or_else(|| renderer.default_font()); -    let size = size.unwrap_or_else(|| renderer.default_size()); -    let (cursor, offset) = if let Some(focus) = &state.is_focused { +    let (cursor, offset) = if let Some(focus) = state +        .is_focused +        .as_ref() +        .filter(|focus| focus.is_window_focused) +    {          match state.cursor.state(value) {              cursor::State::Index(position) => {                  let (text_value_width, offset) =                      measure_cursor_and_scroll_offset( -                        renderer, +                        &state.value,                          text_bounds, -                        value, -                        size,                          position, -                        font,                      );                  let is_cursor_visible = ((focus.now - focus.updated_at) @@ -1062,22 +1148,16 @@ pub fn draw<Renderer>(                  let (left_position, left_offset) =                      measure_cursor_and_scroll_offset( -                        renderer, +                        &state.value,                          text_bounds, -                        value, -                        size,                          left, -                        font,                      );                  let (right_position, right_offset) =                      measure_cursor_and_scroll_offset( -                        renderer, +                        &state.value,                          text_bounds, -                        value, -                        size,                          right, -                        font,                      );                  let width = right_position - left_position; @@ -1109,12 +1189,7 @@ pub fn draw<Renderer>(          (None, 0.0)      }; -    let text_width = renderer.measure_width( -        if text.is_empty() { placeholder } else { &text }, -        size, -        font, -        text::Shaping::Advanced, -    ); +    let text_width = state.value.min_width();      let render = |renderer: &mut Renderer| {          if let Some((cursor, color)) = cursor { @@ -1123,32 +1198,26 @@ pub fn draw<Renderer>(              renderer.with_translation(Vector::ZERO, |_| {});          } -        renderer.fill_text(Text { -            content: if text.is_empty() { placeholder } else { &text }, -            color: if text.is_empty() { +        renderer.fill_paragraph( +            if text.is_empty() { +                &state.placeholder +            } else { +                &state.value +            }, +            Point::new(text_bounds.x, text_bounds.center_y()), +            if text.is_empty() {                  theme.placeholder_color(style)              } else if is_disabled {                  theme.disabled_color(style)              } else {                  theme.value_color(style)              }, -            font, -            bounds: Rectangle { -                y: text_bounds.center_y(), -                width: f32::INFINITY, -                ..text_bounds -            }, -            size, -            line_height, -            horizontal_alignment: alignment::Horizontal::Left, -            vertical_alignment: alignment::Vertical::Center, -            shaping: text::Shaping::Advanced, -        }); +        );      };      if text_width > text_bounds.width {          renderer.with_layer(text_bounds, |renderer| { -            renderer.with_translation(Vector::new(-offset, 0.0), render) +            renderer.with_translation(Vector::new(-offset, 0.0), render);          });      } else {          render(renderer); @@ -1174,7 +1243,10 @@ pub fn mouse_interaction(  /// The state of a [`TextInput`].  #[derive(Debug, Default, Clone)] -pub struct State { +pub struct State<P: text::Paragraph> { +    value: P, +    placeholder: P, +    icon: P,      is_focused: Option<Focus>,      is_dragging: bool,      is_pasting: Option<Value>, @@ -1188,9 +1260,10 @@ pub struct State {  struct Focus {      updated_at: Instant,      now: Instant, +    is_window_focused: bool,  } -impl State { +impl<P: text::Paragraph> State<P> {      /// Creates a new [`State`], representing an unfocused [`TextInput`].      pub fn new() -> Self {          Self::default() @@ -1199,6 +1272,9 @@ impl State {      /// Creates a new [`State`], representing a focused [`TextInput`].      pub fn focused() -> Self {          Self { +            value: P::default(), +            placeholder: P::default(), +            icon: P::default(),              is_focused: None,              is_dragging: false,              is_pasting: None, @@ -1225,6 +1301,7 @@ impl State {          self.is_focused = Some(Focus {              updated_at: now,              now, +            is_window_focused: true,          });          self.move_cursor_to_end(); @@ -1256,35 +1333,35 @@ impl State {      }  } -impl operation::Focusable for State { +impl<P: text::Paragraph> operation::Focusable for State<P> {      fn is_focused(&self) -> bool {          State::is_focused(self)      }      fn focus(&mut self) { -        State::focus(self) +        State::focus(self);      }      fn unfocus(&mut self) { -        State::unfocus(self) +        State::unfocus(self);      }  } -impl operation::TextInput for State { +impl<P: text::Paragraph> operation::TextInput for State<P> {      fn move_cursor_to_front(&mut self) { -        State::move_cursor_to_front(self) +        State::move_cursor_to_front(self);      }      fn move_cursor_to_end(&mut self) { -        State::move_cursor_to_end(self) +        State::move_cursor_to_end(self);      }      fn move_cursor_to(&mut self, position: usize) { -        State::move_cursor_to(self, position) +        State::move_cursor_to(self, position);      }      fn select_all(&mut self) { -        State::select_all(self) +        State::select_all(self);      }  } @@ -1300,17 +1377,11 @@ mod platform {      }  } -fn offset<Renderer>( -    renderer: &Renderer, +fn offset<P: text::Paragraph>(      text_bounds: Rectangle, -    font: Renderer::Font, -    size: f32,      value: &Value, -    state: &State, -) -> f32 -where -    Renderer: text::Renderer, -{ +    state: &State<P>, +) -> f32 {      if state.is_focused() {          let cursor = state.cursor(); @@ -1320,12 +1391,9 @@ where          };          let (_, offset) = measure_cursor_and_scroll_offset( -            renderer, +            &state.value,              text_bounds, -            value, -            size,              focus_position, -            font,          );          offset @@ -1334,72 +1402,72 @@ where      }  } -fn measure_cursor_and_scroll_offset<Renderer>( -    renderer: &Renderer, +fn measure_cursor_and_scroll_offset( +    paragraph: &impl text::Paragraph,      text_bounds: Rectangle, -    value: &Value, -    size: f32,      cursor_index: usize, -    font: Renderer::Font, -) -> (f32, f32) -where -    Renderer: text::Renderer, -{ -    let text_before_cursor = value.until(cursor_index).to_string(); +) -> (f32, f32) { +    let grapheme_position = paragraph +        .grapheme_position(0, cursor_index) +        .unwrap_or(Point::ORIGIN); -    let text_value_width = renderer.measure_width( -        &text_before_cursor, -        size, -        font, -        text::Shaping::Advanced, -    ); - -    let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); +    let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0); -    (text_value_width, offset) +    (grapheme_position.x, offset)  }  /// Computes the position of the text cursor at the given X coordinate of  /// a [`TextInput`]. -fn find_cursor_position<Renderer>( -    renderer: &Renderer, +fn find_cursor_position<P: text::Paragraph>(      text_bounds: Rectangle, -    font: Option<Renderer::Font>, -    size: Option<f32>, -    line_height: text::LineHeight,      value: &Value, -    state: &State, +    state: &State<P>,      x: f32, -) -> Option<usize> -where -    Renderer: text::Renderer, -{ -    let font = font.unwrap_or_else(|| renderer.default_font()); -    let size = size.unwrap_or_else(|| renderer.default_size()); - -    let offset = offset(renderer, text_bounds, font, size, value, state); +) -> Option<usize> { +    let offset = offset(text_bounds, value, state);      let value = value.to_string(); -    let char_offset = renderer -        .hit_test( -            &value, -            size, -            line_height, -            font, -            Size::INFINITY, -            text::Shaping::Advanced, -            Point::new(x + offset, text_bounds.height / 2.0), -            true, -        ) +    let char_offset = state +        .value +        .hit_test(Point::new(x + offset, text_bounds.height / 2.0))          .map(text::Hit::cursor)?;      Some(          unicode_segmentation::UnicodeSegmentation::graphemes( -            &value[..char_offset], +            &value[..char_offset.min(value.len())],              true,          )          .count(),      )  } +fn replace_paragraph<Renderer>( +    renderer: &Renderer, +    state: &mut State<Renderer::Paragraph>, +    layout: Layout<'_>, +    value: &Value, +    font: Option<Renderer::Font>, +    text_size: Option<Pixels>, +    line_height: text::LineHeight, +) where +    Renderer: text::Renderer, +{ +    let font = font.unwrap_or_else(|| renderer.default_font()); +    let text_size = text_size.unwrap_or_else(|| renderer.default_size()); + +    let mut children_layout = layout.children(); +    let text_bounds = children_layout.next().unwrap().bounds(); + +    state.value = Renderer::Paragraph::with_text(Text { +        font, +        line_height, +        content: &value.to_string(), +        bounds: Size::new(f32::INFINITY, text_bounds.height), +        size: text_size, +        horizontal_alignment: alignment::Horizontal::Left, +        vertical_alignment: alignment::Vertical::Top, +        shaping: text::Shaping::Advanced, +    }); +} +  const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; diff --git a/widget/src/text_input/cursor.rs b/widget/src/text_input/cursor.rs index 9680dfd7..f682b17d 100644 --- a/widget/src/text_input/cursor.rs +++ b/widget/src/text_input/cursor.rs @@ -56,7 +56,7 @@ impl Cursor {              State::Selection { start, end } => {                  Some((start.min(end), start.max(end)))              } -            _ => None, +            State::Index(_) => None,          }      } @@ -65,11 +65,11 @@ impl Cursor {      }      pub(crate) fn move_right(&mut self, value: &Value) { -        self.move_right_by_amount(value, 1) +        self.move_right_by_amount(value, 1);      }      pub(crate) fn move_right_by_words(&mut self, value: &Value) { -        self.move_to(value.next_end_of_word(self.right(value))) +        self.move_to(value.next_end_of_word(self.right(value)));      }      pub(crate) fn move_right_by_amount( @@ -79,7 +79,7 @@ impl Cursor {      ) {          match self.state(value) {              State::Index(index) => { -                self.move_to(index.saturating_add(amount).min(value.len())) +                self.move_to(index.saturating_add(amount).min(value.len()));              }              State::Selection { start, end } => self.move_to(end.max(start)),          } @@ -89,7 +89,7 @@ impl Cursor {          match self.state(value) {              State::Index(index) if index > 0 => self.move_to(index - 1),              State::Selection { start, end } => self.move_to(start.min(end)), -            _ => self.move_to(0), +            State::Index(_) => self.move_to(0),          }      } @@ -108,10 +108,10 @@ impl Cursor {      pub(crate) fn select_left(&mut self, value: &Value) {          match self.state(value) {              State::Index(index) if index > 0 => { -                self.select_range(index, index - 1) +                self.select_range(index, index - 1);              }              State::Selection { start, end } if end > 0 => { -                self.select_range(start, end - 1) +                self.select_range(start, end - 1);              }              _ => {}          } @@ -120,10 +120,10 @@ impl Cursor {      pub(crate) fn select_right(&mut self, value: &Value) {          match self.state(value) {              State::Index(index) if index < value.len() => { -                self.select_range(index, index + 1) +                self.select_range(index, index + 1);              }              State::Selection { start, end } if end < value.len() => { -                self.select_range(start, end + 1) +                self.select_range(start, end + 1);              }              _ => {}          } @@ -132,10 +132,10 @@ impl Cursor {      pub(crate) fn select_left_by_words(&mut self, value: &Value) {          match self.state(value) {              State::Index(index) => { -                self.select_range(index, value.previous_start_of_word(index)) +                self.select_range(index, value.previous_start_of_word(index));              }              State::Selection { start, end } => { -                self.select_range(start, value.previous_start_of_word(end)) +                self.select_range(start, value.previous_start_of_word(end));              }          }      } @@ -143,10 +143,10 @@ impl Cursor {      pub(crate) fn select_right_by_words(&mut self, value: &Value) {          match self.state(value) {              State::Index(index) => { -                self.select_range(index, value.next_end_of_word(index)) +                self.select_range(index, value.next_end_of_word(index));              }              State::Selection { start, end } => { -                self.select_range(start, value.next_end_of_word(end)) +                self.select_range(start, value.next_end_of_word(end));              }          }      } diff --git a/widget/src/text_input/value.rs b/widget/src/text_input/value.rs index cf4da562..46a1f754 100644 --- a/widget/src/text_input/value.rs +++ b/widget/src/text_input/value.rs @@ -2,7 +2,7 @@ use unicode_segmentation::UnicodeSegmentation;  /// The value of a [`TextInput`].  /// -/// [`TextInput`]: crate::widget::TextInput +/// [`TextInput`]: super::TextInput  // TODO: Reduce allocations, cache results (?)  #[derive(Debug, Clone)]  pub struct Value { @@ -89,11 +89,6 @@ impl Value {          Self { graphemes }      } -    /// Converts the [`Value`] into a `String`. -    pub fn to_string(&self) -> String { -        self.graphemes.concat() -    } -      /// Inserts a new `char` at the given grapheme `index`.      pub fn insert(&mut self, index: usize, c: char) {          self.graphemes.insert(index, c.to_string()); @@ -131,3 +126,9 @@ impl Value {          }      }  } + +impl std::fmt::Display for Value { +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        f.write_str(&self.graphemes.concat()) +    } +} | 
