From 66d671066386cd4ec1addbdfe0750e3077a5ea51 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 13 Jul 2023 12:10:10 -0700 Subject: Dont blink input cursor when window loses focus --- widget/src/text_input.rs | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 272263f9..bd3145ea 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -564,6 +564,7 @@ where Some(Focus { updated_at: now, now, + is_window_focused: true, }) }) } else { @@ -919,19 +920,35 @@ 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; + } + } 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), + )); + } } } _ => {} @@ -1016,7 +1033,11 @@ pub fn draw( 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) = @@ -1188,6 +1209,7 @@ pub struct State { struct Focus { updated_at: Instant, now: Instant, + is_window_focused: bool, } impl State { @@ -1225,6 +1247,7 @@ impl State { self.is_focused = Some(Focus { updated_at: now, now, + is_window_focused: true, }); self.move_cursor_to_end(); -- cgit From 44c07323067fa8c09122356c111047082d946c59 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 13 Jul 2023 12:21:24 -0700 Subject: Restart animation when regaining focus --- widget/src/text_input.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index bd3145ea..a335afbc 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -932,6 +932,9 @@ where 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)) => { -- cgit From 42c423b4a89613c4e1c552c891c1391a34837122 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 15 Jul 2023 10:04:25 -0700 Subject: Add viewport to Widget::on_event --- widget/src/text_input.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index a335afbc..9958cbcc 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -302,6 +302,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { update( event, -- cgit From 9eb2889d09e42b250f12be9ba9ef8a470d8eeeae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 22:33:33 +0200 Subject: Use default padding of `TextInput` in `ComboBox` --- widget/src/text_input.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 9958cbcc..b899eb67 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -76,6 +76,9 @@ where style: ::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, -- cgit From c81f4676fb39f7ac4a9129be3fbabe7888a88042 Mon Sep 17 00:00:00 2001 From: Casper Rogild Storm Date: Sat, 5 Aug 2023 21:47:02 +0200 Subject: ensure no paste with alt --- widget/src/text_input.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index b899eb67..ef6d31ac 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -842,7 +842,9 @@ where shell.publish(message); } 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 => { -- cgit From 36120d5685048f761caf4b6a12a4f3a6007f9363 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 26 Aug 2023 01:31:11 +0200 Subject: Run `cargo fmt` with Rust 1.72 --- widget/src/text_input.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ef6d31ac..61fc0055 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -693,7 +693,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() @@ -716,7 +718,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(); -- cgit From ed3454301e663a7cb7d73cd56b57b188f4d14a2f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 04:31:21 +0200 Subject: Implement explicit text caching in the widget state tree --- widget/src/text_input.rs | 322 ++++++++++++++++++++++++----------------------- 1 file changed, 164 insertions(+), 158 deletions(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 61fc0055..209ef968 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; @@ -30,6 +30,8 @@ use crate::core::{ }; use crate::runtime::Command; +use std::cell::RefCell; + pub use iced_style::text_input::{Appearance, StyleSheet}; /// A field that can be filled with text. @@ -67,7 +69,7 @@ where font: Option, width: Length, padding: Padding, - size: Option, + size: Option, line_height: text::LineHeight, on_input: Option Message + 'a>>, on_paste: Option Message + 'a>>, @@ -178,7 +180,7 @@ where /// Sets the text size of the [`TextInput`]. pub fn size(mut self, size: impl Into) -> Self { - self.size = Some(size.into().0); + self.size = Some(size.into()); self } @@ -218,12 +220,8 @@ where theme, layout, cursor, - tree.state.downcast_ref::(), + tree.state.downcast_ref::>(), 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(), @@ -240,15 +238,15 @@ where Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { - tree::Tag::of::() + tree::Tag::of::>() } fn state(&self) -> tree::State { - tree::State::new(State::new()) + tree::State::new(State::::new()) } fn diff(&self, tree: &mut Tree) { - let state = tree.state.downcast_mut::(); + let state = tree.state.downcast_mut::>(); // Unfocus text input if it becomes disabled if self.on_input.is_none() { @@ -269,6 +267,7 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -278,8 +277,13 @@ where self.width, self.padding, self.size, + self.font, self.line_height, self.icon.as_ref(), + tree.state.downcast_ref::>(), + &self.value, + &self.placeholder, + self.is_secure, ) } @@ -290,7 +294,7 @@ where _renderer: &Renderer, operation: &mut dyn Operation, ) { - let state = tree.state.downcast_mut::(); + let state = tree.state.downcast_mut::>(); operation.focusable(state, self.id.as_ref().map(|id| &id.0)); operation.text_input(state, self.id.as_ref().map(|id| &id.0)); @@ -322,7 +326,7 @@ where self.on_input.as_deref(), self.on_paste.as_deref(), &self.on_submit, - || tree.state.downcast_mut::(), + || tree.state.downcast_mut::>(), ) } @@ -341,12 +345,8 @@ where theme, layout, cursor, - tree.state.downcast_ref::(), + tree.state.downcast_ref::>(), &self.value, - &self.placeholder, - self.size, - self.line_height, - self.font, self.on_input.is_none(), self.is_secure, self.icon.as_ref(), @@ -388,7 +388,7 @@ pub struct Icon { /// The unicode code point that will be used as the icon. pub code_point: char, /// The font size of the content. - pub size: Option, + pub size: Option, /// The spacing between the [`Icon`] and the text in a [`TextInput`]. pub spacing: f32, /// The side of a [`TextInput`] where to display the [`Icon`]. @@ -465,29 +465,65 @@ pub fn layout( limits: &layout::Limits, width: Length, padding: Padding, - size: Option, + size: Option, + font: Option, line_height: text::LineHeight, icon: Option<&Icon>, + state: &State, + 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); - 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 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, + }; + + renderer.update_paragraph( + &mut state.placeholder_paragraph.borrow_mut(), + placeholder_text, + ); + + if is_secure { + renderer.update_paragraph( + &mut state.paragraph.borrow_mut(), + Text { + content: &value.secure().to_string(), + ..placeholder_text + }, + ); + } else { + renderer.update_paragraph( + &mut state.paragraph.borrow_mut(), + Text { + content: &value.to_string(), + ..placeholder_text + }, ); + } + + if let Some(icon) = icon { + let icon_width = 0.0; // TODO let mut text_node = layout::Node::new( text_bounds - Size::new(icon_width + icon.spacing, 0.0), @@ -537,19 +573,31 @@ pub fn update<'a, Message, Renderer>( clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, value: &mut Value, - size: Option, + size: Option, line_height: text::LineHeight, font: Option, is_secure: bool, on_input: Option<&dyn Fn(String) -> Message>, on_paste: Option<&dyn Fn(String) -> Message>, on_submit: &Option, - state: impl FnOnce() -> &'a mut State, + state: impl FnOnce() -> &'a mut State, ) -> 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 { .. }) => { @@ -592,11 +640,7 @@ where }; find_cursor_position( - renderer, text_layout.bounds(), - font, - size, - line_height, &value, state, target, @@ -621,11 +665,7 @@ where state.cursor.select_all(value); } else { let position = find_cursor_position( - renderer, text_layout.bounds(), - font, - size, - line_height, value, state, target, @@ -671,11 +711,7 @@ where }; let position = find_cursor_position( - renderer, text_layout.bounds(), - font, - size, - line_height, &value, state, target, @@ -710,6 +746,8 @@ where focus.updated_at = Instant::now(); + update_cache(state, value); + return event::Status::Captured; } } @@ -749,6 +787,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) @@ -769,6 +809,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) @@ -844,6 +886,8 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + + update_cache(state, value); } keyboard::KeyCode::V => { if state.keyboard_modifiers.command() @@ -876,6 +920,8 @@ where shell.publish(message); state.is_pasting = Some(content); + + update_cache(state, value); } else { state.is_pasting = None; } @@ -979,12 +1025,8 @@ pub fn draw( theme: &Renderer::Theme, layout: Layout<'_>, cursor: mouse::Cursor, - state: &State, + state: &State, value: &Value, - placeholder: &str, - size: Option, - line_height: text::LineHeight, - font: Option, is_disabled: bool, is_secure: bool, icon: Option<&Icon>, @@ -1023,28 +1065,14 @@ pub fn draw( appearance.background, ); - if let Some(icon) = icon { - let icon_layout = children_layout.next().unwrap(); + if let Some(_icon) = icon { + 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, - }); + // TODO } let text = value.to_string(); - let font = font.unwrap_or_else(|| renderer.default_font()); - let size = size.unwrap_or_else(|| renderer.default_size()); + let paragraph = &state.paragraph.borrow() as &Renderer::Paragraph; let (cursor, offset) = if let Some(focus) = state .is_focused @@ -1055,12 +1083,9 @@ pub fn draw( cursor::State::Index(position) => { let (text_value_width, offset) = measure_cursor_and_scroll_offset( - renderer, + paragraph, text_bounds, - value, - size, position, - font, ); let is_cursor_visible = ((focus.now - focus.updated_at) @@ -1096,22 +1121,16 @@ pub fn draw( let (left_position, left_offset) = measure_cursor_and_scroll_offset( - renderer, + paragraph, text_bounds, - value, - size, left, - font, ); let (right_position, right_offset) = measure_cursor_and_scroll_offset( - renderer, + paragraph, text_bounds, - value, - size, right, - font, ); let width = right_position - left_position; @@ -1143,12 +1162,7 @@ pub fn draw( (None, 0.0) }; - let text_width = renderer.measure_width( - if text.is_empty() { placeholder } else { &text }, - size, - font, - text::Shaping::Advanced, - ); + let text_width = paragraph.min_width(); let render = |renderer: &mut Renderer| { if let Some((cursor, color)) = cursor { @@ -1157,27 +1171,23 @@ pub fn draw( renderer.with_translation(Vector::ZERO, |_| {}); } - renderer.fill_text(Text { - content: if text.is_empty() { placeholder } else { &text }, - color: if text.is_empty() { + let placeholder_paragraph = state.placeholder_paragraph.borrow(); + + renderer.fill_paragraph( + if text.is_empty() { + &placeholder_paragraph + } else { + paragraph + }, + 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 { @@ -1208,7 +1218,9 @@ pub fn mouse_interaction( /// The state of a [`TextInput`]. #[derive(Debug, Default, Clone)] -pub struct State { +pub struct State { + paragraph: RefCell

, + placeholder_paragraph: RefCell

, is_focused: Option, is_dragging: bool, is_pasting: Option, @@ -1225,7 +1237,7 @@ struct Focus { is_window_focused: bool, } -impl State { +impl State

{ /// Creates a new [`State`], representing an unfocused [`TextInput`]. pub fn new() -> Self { Self::default() @@ -1234,6 +1246,8 @@ impl State { /// Creates a new [`State`], representing a focused [`TextInput`]. pub fn focused() -> Self { Self { + paragraph: RefCell::new(P::default()), + placeholder_paragraph: RefCell::new(P::default()), is_focused: None, is_dragging: false, is_pasting: None, @@ -1292,7 +1306,7 @@ impl State { } } -impl operation::Focusable for State { +impl operation::Focusable for State

{ fn is_focused(&self) -> bool { State::is_focused(self) } @@ -1306,7 +1320,7 @@ impl operation::Focusable for State { } } -impl operation::TextInput for State { +impl operation::TextInput for State

{ fn move_cursor_to_front(&mut self) { State::move_cursor_to_front(self) } @@ -1336,17 +1350,11 @@ mod platform { } } -fn offset( - renderer: &Renderer, +fn offset( text_bounds: Rectangle, - font: Renderer::Font, - size: f32, value: &Value, - state: &State, -) -> f32 -where - Renderer: text::Renderer, -{ + state: &State

, +) -> f32 { if state.is_focused() { let cursor = state.cursor(); @@ -1356,12 +1364,9 @@ where }; let (_, offset) = measure_cursor_and_scroll_offset( - renderer, + &state.paragraph.borrow() as &P, text_bounds, - value, - size, focus_position, - font, ); offset @@ -1370,63 +1375,35 @@ where } } -fn measure_cursor_and_scroll_offset( - 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 = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0); - let offset = ((text_value_width + 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, +fn find_cursor_position( text_bounds: Rectangle, - font: Option, - size: Option, - line_height: text::LineHeight, value: &Value, - state: &State, + state: &State

, x: f32, -) -> Option -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 { + 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 + .paragraph + .borrow() + .hit_test(Point::new(x + offset, text_bounds.height / 2.0)) .map(text::Hit::cursor)?; Some( @@ -1438,4 +1415,33 @@ where ) } +fn replace_paragraph( + renderer: &Renderer, + state: &mut State, + layout: Layout<'_>, + value: &Value, + font: Option, + text_size: Option, + 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.paragraph.get_mut() = renderer.create_paragraph(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; -- cgit From a026e917d3364e58fd827995261158d8cb356ce9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 06:36:24 +0200 Subject: Make `widget::Tree` mutable in `Widget::layout` --- widget/src/text_input.rs | 46 +++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 209ef968..75800a34 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -30,8 +30,6 @@ use crate::core::{ }; use crate::runtime::Command; -use std::cell::RefCell; - pub use iced_style::text_input::{Appearance, StyleSheet}; /// A field that can be filled with text. @@ -267,7 +265,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -280,7 +278,7 @@ where self.font, self.line_height, self.icon.as_ref(), - tree.state.downcast_ref::>(), + tree.state.downcast_mut::>(), &self.value, &self.placeholder, self.is_secure, @@ -469,7 +467,7 @@ pub fn layout( font: Option, line_height: text::LineHeight, icon: Option<&Icon>, - state: &State, + state: &mut State, value: &Value, placeholder: &str, is_secure: bool, @@ -499,14 +497,12 @@ where shaping: text::Shaping::Advanced, }; - renderer.update_paragraph( - &mut state.placeholder_paragraph.borrow_mut(), - placeholder_text, - ); + renderer + .update_paragraph(&mut state.placeholder_paragraph, placeholder_text); if is_secure { renderer.update_paragraph( - &mut state.paragraph.borrow_mut(), + &mut state.paragraph, Text { content: &value.secure().to_string(), ..placeholder_text @@ -514,7 +510,7 @@ where ); } else { renderer.update_paragraph( - &mut state.paragraph.borrow_mut(), + &mut state.paragraph, Text { content: &value.to_string(), ..placeholder_text @@ -1072,7 +1068,6 @@ pub fn draw( } let text = value.to_string(); - let paragraph = &state.paragraph.borrow() as &Renderer::Paragraph; let (cursor, offset) = if let Some(focus) = state .is_focused @@ -1083,7 +1078,7 @@ pub fn draw( cursor::State::Index(position) => { let (text_value_width, offset) = measure_cursor_and_scroll_offset( - paragraph, + &state.paragraph, text_bounds, position, ); @@ -1121,14 +1116,14 @@ pub fn draw( let (left_position, left_offset) = measure_cursor_and_scroll_offset( - paragraph, + &state.paragraph, text_bounds, left, ); let (right_position, right_offset) = measure_cursor_and_scroll_offset( - paragraph, + &state.paragraph, text_bounds, right, ); @@ -1162,7 +1157,7 @@ pub fn draw( (None, 0.0) }; - let text_width = paragraph.min_width(); + let text_width = state.paragraph.min_width(); let render = |renderer: &mut Renderer| { if let Some((cursor, color)) = cursor { @@ -1171,13 +1166,11 @@ pub fn draw( renderer.with_translation(Vector::ZERO, |_| {}); } - let placeholder_paragraph = state.placeholder_paragraph.borrow(); - renderer.fill_paragraph( if text.is_empty() { - &placeholder_paragraph + &state.placeholder_paragraph } else { - paragraph + &state.paragraph }, Point::new(text_bounds.x, text_bounds.center_y()), if text.is_empty() { @@ -1219,8 +1212,8 @@ pub fn mouse_interaction( /// The state of a [`TextInput`]. #[derive(Debug, Default, Clone)] pub struct State { - paragraph: RefCell

, - placeholder_paragraph: RefCell

, + paragraph: P, + placeholder_paragraph: P, is_focused: Option, is_dragging: bool, is_pasting: Option, @@ -1246,8 +1239,8 @@ impl State

{ /// Creates a new [`State`], representing a focused [`TextInput`]. pub fn focused() -> Self { Self { - paragraph: RefCell::new(P::default()), - placeholder_paragraph: RefCell::new(P::default()), + paragraph: P::default(), + placeholder_paragraph: P::default(), is_focused: None, is_dragging: false, is_pasting: None, @@ -1364,7 +1357,7 @@ fn offset( }; let (_, offset) = measure_cursor_and_scroll_offset( - &state.paragraph.borrow() as &P, + &state.paragraph, text_bounds, focus_position, ); @@ -1402,7 +1395,6 @@ fn find_cursor_position( let char_offset = state .paragraph - .borrow() .hit_test(Point::new(x + offset, text_bounds.height / 2.0)) .map(text::Hit::cursor)?; @@ -1432,7 +1424,7 @@ fn replace_paragraph( let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - *state.paragraph.get_mut() = renderer.create_paragraph(Text { + state.paragraph = renderer.create_paragraph(Text { font, line_height, content: &value.to_string(), -- cgit From bcd9fdb521ce4cf21e9d1ee28157ed068ae1428c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 06:43:45 +0200 Subject: Simplify new logic in `TextInput` --- widget/src/text_input.rs | 58 +++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 33 deletions(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 75800a34..f36b5616 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -497,26 +497,18 @@ where shaping: text::Shaping::Advanced, }; - renderer - .update_paragraph(&mut state.placeholder_paragraph, placeholder_text); - - if is_secure { - renderer.update_paragraph( - &mut state.paragraph, - Text { - content: &value.secure().to_string(), - ..placeholder_text - }, - ); - } else { - renderer.update_paragraph( - &mut state.paragraph, - Text { - content: &value.to_string(), - ..placeholder_text - }, - ); - } + renderer.update_paragraph(&mut state.placeholder, placeholder_text); + + let secure_value = is_secure.then(|| value.secure()); + let value = secure_value.as_ref().unwrap_or(value); + + renderer.update_paragraph( + &mut state.value, + Text { + content: &value.to_string(), + ..placeholder_text + }, + ); if let Some(icon) = icon { let icon_width = 0.0; // TODO @@ -1078,7 +1070,7 @@ pub fn draw( cursor::State::Index(position) => { let (text_value_width, offset) = measure_cursor_and_scroll_offset( - &state.paragraph, + &state.value, text_bounds, position, ); @@ -1116,14 +1108,14 @@ pub fn draw( let (left_position, left_offset) = measure_cursor_and_scroll_offset( - &state.paragraph, + &state.value, text_bounds, left, ); let (right_position, right_offset) = measure_cursor_and_scroll_offset( - &state.paragraph, + &state.value, text_bounds, right, ); @@ -1157,7 +1149,7 @@ pub fn draw( (None, 0.0) }; - let text_width = state.paragraph.min_width(); + let text_width = state.value.min_width(); let render = |renderer: &mut Renderer| { if let Some((cursor, color)) = cursor { @@ -1168,9 +1160,9 @@ pub fn draw( renderer.fill_paragraph( if text.is_empty() { - &state.placeholder_paragraph + &state.placeholder } else { - &state.paragraph + &state.value }, Point::new(text_bounds.x, text_bounds.center_y()), if text.is_empty() { @@ -1212,8 +1204,8 @@ pub fn mouse_interaction( /// The state of a [`TextInput`]. #[derive(Debug, Default, Clone)] pub struct State { - paragraph: P, - placeholder_paragraph: P, + value: P, + placeholder: P, is_focused: Option, is_dragging: bool, is_pasting: Option, @@ -1239,8 +1231,8 @@ impl State

{ /// Creates a new [`State`], representing a focused [`TextInput`]. pub fn focused() -> Self { Self { - paragraph: P::default(), - placeholder_paragraph: P::default(), + value: P::default(), + placeholder: P::default(), is_focused: None, is_dragging: false, is_pasting: None, @@ -1357,7 +1349,7 @@ fn offset( }; let (_, offset) = measure_cursor_and_scroll_offset( - &state.paragraph, + &state.value, text_bounds, focus_position, ); @@ -1394,7 +1386,7 @@ fn find_cursor_position( let value = value.to_string(); let char_offset = state - .paragraph + .value .hit_test(Point::new(x + offset, text_bounds.height / 2.0)) .map(text::Hit::cursor)?; @@ -1424,7 +1416,7 @@ fn replace_paragraph( let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - state.paragraph = renderer.create_paragraph(Text { + state.value = renderer.create_paragraph(Text { font, line_height, content: &value.to_string(), -- cgit From 3cc605b70f543313cb665465ac169d0c85c446ab Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 9 Sep 2023 12:36:00 +0200 Subject: Implement `Icon` support for `TextInput` --- widget/src/text_input.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index f36b5616..aa35b5e4 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -511,7 +511,20 @@ where ); if let Some(icon) = icon { - let icon_width = 0.0; // TODO + 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, + }; + + renderer.update_paragraph(&mut state.icon, 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), @@ -1053,10 +1066,14 @@ pub fn draw( appearance.background, ); - if let Some(_icon) = icon { - let _icon_layout = children_layout.next().unwrap(); + if icon.is_some() { + let icon_layout = children_layout.next().unwrap(); - // TODO + renderer.fill_paragraph( + &state.icon, + icon_layout.bounds().center(), + appearance.icon_color, + ); } let text = value.to_string(); @@ -1206,6 +1223,7 @@ pub fn mouse_interaction( pub struct State { value: P, placeholder: P, + icon: P, is_focused: Option, is_dragging: bool, is_pasting: Option, @@ -1233,6 +1251,7 @@ impl State

{ Self { value: P::default(), placeholder: P::default(), + icon: P::default(), is_focused: None, is_dragging: false, is_pasting: None, -- cgit From 89d9f1d7d2202029028a487df1dd11b0665a7517 Mon Sep 17 00:00:00 2001 From: Matthias Vogelgesang Date: Sat, 9 Sep 2023 12:24:47 +0200 Subject: Fix majority of unresolved documentation links --- widget/src/text_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 61fc0055..f7a90880 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -182,7 +182,7 @@ where self } - /// Sets the [`LineHeight`] of the [`TextInput`]. + /// Sets the [`text::LineHeight`] of the [`TextInput`]. pub fn line_height( mut self, line_height: impl Into, -- cgit From bc1bde0d5ca1ec291f13e108f1543daa75b97848 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 10 Sep 2023 03:36:31 +0200 Subject: Fix `ComboBox` widget not displaying selection text --- widget/src/text_input.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index bfd196fd..7d5ae806 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -200,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::>(), + value.unwrap_or(&self.value), + &self.placeholder, + self.is_secure, + ) + } + /// Draws the [`TextInput`] with the given [`Renderer`], overriding its /// [`Value`] if provided. /// @@ -1411,7 +1437,7 @@ fn find_cursor_position( Some( unicode_segmentation::UnicodeSegmentation::graphemes( - &value[..char_offset], + &value[..char_offset.min(value.len())], true, ) .count(), -- cgit From 346af3f8b0baa418fd37b878bc2930ff0bd57cc0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 11 Sep 2023 02:47:24 +0200 Subject: Make `FontSystem` global and simplify `Paragraph` API --- widget/src/text_input.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 7d5ae806..f9a2d419 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -523,18 +523,15 @@ where shaping: text::Shaping::Advanced, }; - renderer.update_paragraph(&mut state.placeholder, placeholder_text); + state.placeholder.update(placeholder_text); let secure_value = is_secure.then(|| value.secure()); let value = secure_value.as_ref().unwrap_or(value); - renderer.update_paragraph( - &mut state.value, - Text { - content: &value.to_string(), - ..placeholder_text - }, - ); + state.value.update(Text { + content: &value.to_string(), + ..placeholder_text + }); if let Some(icon) = icon { let icon_text = Text { @@ -548,7 +545,7 @@ where shaping: text::Shaping::Advanced, }; - renderer.update_paragraph(&mut state.icon, icon_text); + state.icon.update(icon_text); let icon_width = state.icon.min_width(); @@ -1461,7 +1458,7 @@ fn replace_paragraph( let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - state.value = renderer.create_paragraph(Text { + state.value = Renderer::Paragraph::with_text(Text { font, line_height, content: &value.to_string(), -- cgit From 34f07b60273d6cfe13834af54cd0e24d34569387 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 04:11:52 +0200 Subject: Fix `clippy::semicolon_if_nothing_returned` --- widget/src/text_input.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'widget/src/text_input.rs') diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 7d5ae806..9e1fb796 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -250,7 +250,7 @@ where self.is_secure, self.icon.as_ref(), &self.style, - ) + ); } } @@ -375,7 +375,7 @@ where self.is_secure, self.icon.as_ref(), &self.style, - ) + ); } fn mouse_interaction( @@ -622,7 +622,7 @@ where font, size, line_height, - ) + ); }; match event { @@ -849,7 +849,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); } @@ -864,7 +864,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); } @@ -1220,7 +1220,7 @@ pub fn draw( 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); @@ -1342,29 +1342,29 @@ impl operation::Focusable for State

{ } 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

{ 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); } } -- cgit