diff options
-rw-r--r-- | examples/styling/src/main.rs | 6 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 36 | ||||
-rw-r--r-- | graphics/src/widget/text.rs | 44 | ||||
-rw-r--r-- | graphics/src/widget/text_input.rs | 75 | ||||
-rw-r--r-- | native/src/overlay/menu.rs | 2 | ||||
-rw-r--r-- | native/src/renderer/null.rs | 24 | ||||
-rw-r--r-- | native/src/renderer/text.rs | 40 | ||||
-rw-r--r-- | native/src/widget/checkbox.rs | 9 | ||||
-rw-r--r-- | native/src/widget/pick_list.rs | 2 | ||||
-rw-r--r-- | native/src/widget/radio.rs | 9 | ||||
-rw-r--r-- | native/src/widget/text.rs | 48 | ||||
-rw-r--r-- | native/src/widget/text_input.rs | 395 | ||||
-rw-r--r-- | native/src/widget/toggler.rs | 9 | ||||
-rw-r--r-- | native/src/widget/tooltip.rs | 4 | ||||
-rw-r--r-- | style/src/text_input.rs | 13 | ||||
-rw-r--r-- | web/src/widget/text_input.rs | 6 |
16 files changed, 382 insertions, 340 deletions
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 34d18fc9..cca94c8f 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -77,7 +77,7 @@ impl Sandbox for Styling { ) .padding(10) .size(20) - .style(self.theme); + .style(self.theme.into()); let button = Button::new(&mut self.button, Text::new("Submit")) .padding(10) @@ -194,11 +194,11 @@ mod style { } } - impl From<Theme> for Box<dyn text_input::StyleSheet> { + impl From<Theme> for &'static dyn text_input::StyleSheet { fn from(theme: Theme) -> Self { match theme { Theme::Light => Default::default(), - Theme::Dark => dark::TextInput.into(), + Theme::Dark => &dark::TextInput, } } } diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 0dca685f..f8c67047 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -2,7 +2,7 @@ use crate::backend::{self, Backend}; use crate::{Primitive, Vector}; use iced_native::layout; use iced_native::renderer; -use iced_native::{Element, Font, Rectangle}; +use iced_native::{Element, Font, Point, Rectangle, Size}; pub use iced_native::renderer::Style; @@ -91,6 +91,40 @@ where { type Font = Font; + fn default_size(&self) -> u16 { + self.backend().default_size() + } + + fn measure( + &self, + content: &str, + size: u16, + font: Font, + bounds: Size, + ) -> (f32, f32) { + self.backend() + .measure(content, f32::from(size), font, bounds) + } + + fn hit_test( + &self, + content: &str, + size: f32, + font: Font, + bounds: Size, + point: Point, + nearest_only: bool, + ) -> Option<renderer::text::Hit> { + self.backend().hit_test( + content, + size, + font, + bounds, + point, + nearest_only, + ) + } + fn fill_text(&mut self, text: renderer::text::Section<'_, Self::Font>) { self.primitives.push(Primitive::Text { content: text.content.to_string(), diff --git a/graphics/src/widget/text.rs b/graphics/src/widget/text.rs index 4ee2c616..ec0349f9 100644 --- a/graphics/src/widget/text.rs +++ b/graphics/src/widget/text.rs @@ -1,51 +1,7 @@ //! Write some text for your users to read. -use crate::backend::{self, Backend}; use crate::Renderer; -use iced_native::text; -use iced_native::{Font, Point, Size}; /// A paragraph of text. /// /// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`. pub type Text<Backend> = iced_native::Text<Renderer<Backend>>; - -use std::f32; - -impl<B> text::Renderer for Renderer<B> -where - B: Backend + backend::Text, -{ - fn default_size(&self) -> u16 { - self.backend().default_size() - } - - fn measure( - &self, - content: &str, - size: u16, - font: Font, - bounds: Size, - ) -> (f32, f32) { - self.backend() - .measure(content, f32::from(size), font, bounds) - } - - fn hit_test( - &self, - content: &str, - size: f32, - font: Font, - bounds: Size, - point: Point, - nearest_only: bool, - ) -> Option<text::Hit> { - self.backend().hit_test( - content, - size, - font, - bounds, - point, - nearest_only, - ) - } -} diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs index e9dbf056..ebb9138f 100644 --- a/graphics/src/widget/text_input.rs +++ b/graphics/src/widget/text_input.rs @@ -1,11 +1,7 @@ //! Display fields that can be filled with text. //! //! A [`TextInput`] has some local [`State`]. -use crate::backend::{self, Backend}; -use crate::{Font, Rectangle, Renderer, Size}; - -use iced_native::text_input::{self, cursor}; -use std::f32; +use crate::Renderer; pub use iced_native::text_input::State; pub use iced_style::text_input::{Style, StyleSheet}; @@ -15,72 +11,3 @@ pub use iced_style::text_input::{Style, StyleSheet}; /// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`. pub type TextInput<'a, Message, Backend> = iced_native::TextInput<'a, Message, Renderer<Backend>>; - -impl<B> text_input::Renderer for Renderer<B> -where - B: Backend + backend::Text, -{ - type Style = Box<dyn StyleSheet>; - - fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 { - let backend = self.backend(); - - let (width, _) = - backend.measure(value, f32::from(size), font, Size::INFINITY); - - width - } - - fn offset( - &self, - text_bounds: Rectangle, - font: Font, - size: u16, - value: &text_input::Value, - state: &text_input::State, - ) -> f32 { - if state.is_focused() { - let cursor = state.cursor(); - - let focus_position = match cursor.state(value) { - cursor::State::Index(i) => i, - cursor::State::Selection { end, .. } => end, - }; - - let (_, offset) = measure_cursor_and_scroll_offset( - self, - text_bounds, - value, - size, - focus_position, - font, - ); - - offset - } else { - 0.0 - } - } -} - -fn measure_cursor_and_scroll_offset<B>( - renderer: &Renderer<B>, - text_bounds: Rectangle, - value: &text_input::Value, - size: u16, - cursor_index: usize, - font: Font, -) -> (f32, f32) -where - B: Backend + backend::Text, -{ - use iced_native::text_input::Renderer; - - let text_before_cursor = value.until(cursor_index).to_string(); - - let text_value_width = - renderer.measure_value(&text_before_cursor, size, font); - let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); - - (text_value_width, offset) -} diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index a6527a8d..0c335bb6 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -395,7 +395,7 @@ where /// able to use a [`Menu`] in your user interface. /// /// [renderer]: crate::renderer -pub trait Renderer: text::Renderer { +pub trait Renderer: renderer::Text { /// The [`Menu`] style supported by this renderer. type Style: Default + Clone; } diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 2d6979e3..084b8d6f 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -4,7 +4,6 @@ use crate::progress_bar; use crate::radio; use crate::renderer::{self, Renderer}; use crate::text; -use crate::text_input; use crate::toggler; use crate::{Font, Point, Rectangle, Size, Vector}; @@ -38,10 +37,6 @@ impl Renderer for Null { impl renderer::Text for Null { type Font = Font; - fn fill_text(&mut self, _text: renderer::text::Section<'_, Self::Font>) {} -} - -impl text::Renderer for Null { fn default_size(&self) -> u16 { 20 } @@ -67,25 +62,8 @@ impl text::Renderer for Null { ) -> Option<text::Hit> { None } -} -impl text_input::Renderer for Null { - type Style = (); - - fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 { - 0.0 - } - - fn offset( - &self, - _text_bounds: Rectangle, - _font: Font, - _size: u16, - _value: &text_input::Value, - _state: &text_input::State, - ) -> f32 { - 0.0 - } + fn fill_text(&mut self, _text: renderer::text::Section<'_, Self::Font>) {} } impl radio::Renderer for Null { diff --git a/native/src/renderer/text.rs b/native/src/renderer/text.rs index 9234c587..80769b62 100644 --- a/native/src/renderer/text.rs +++ b/native/src/renderer/text.rs @@ -1,10 +1,48 @@ use crate::alignment; -use crate::{Color, Rectangle, Renderer}; +use crate::{Color, Point, Rectangle, Renderer, Size}; + +pub use crate::text::Hit; pub trait Text: Renderer { /// The font type used. type Font: Default + Copy; + /// Returns the default size of [`Text`]. + fn default_size(&self) -> u16; + + /// Measures the text in the given bounds and returns the minimum boundaries + /// that can fit the contents. + fn measure( + &self, + content: &str, + size: u16, + font: Self::Font, + bounds: Size, + ) -> (f32, f32); + + fn measure_width(&self, content: &str, size: u16, font: Self::Font) -> f32 { + let (width, _) = self.measure(content, size, font, Size::INFINITY); + + width + } + + /// Tests whether the provided point is within the boundaries of [`Text`] + /// laid out with the given parameters, returning information about + /// the nearest character. + /// + /// If `nearest_only` is true, the hit test does not consider whether the + /// the point is interior to any glyph bounds, returning only the character + /// with the nearest centeroid. + fn hit_test( + &self, + contents: &str, + size: f32, + font: Self::Font, + bounds: Size, + point: Point, + nearest_only: bool, + ) -> Option<Hit>; + fn fill_text(&mut self, section: Section<'_, Self::Font>); } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 52113322..c307d776 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -5,7 +5,6 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; -use crate::text; use crate::touch; use crate::{ Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point, @@ -30,7 +29,7 @@ use crate::{ /// ///  #[allow(missing_debug_implementations)] -pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> { +pub struct Checkbox<Message, Renderer: self::Renderer + renderer::Text> { is_checked: bool, on_toggle: Box<dyn Fn(bool) -> Message>, label: String, @@ -43,7 +42,7 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> { style: Renderer::Style, } -impl<Message, Renderer: self::Renderer + text::Renderer> +impl<Message, Renderer: self::Renderer + renderer::Text> Checkbox<Message, Renderer> { /// Creates a new [`Checkbox`]. @@ -120,7 +119,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer> impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message, Renderer> where - Renderer: self::Renderer + text::Renderer, + Renderer: self::Renderer + renderer::Text, { fn width(&self) -> Length { self.width @@ -239,7 +238,7 @@ pub trait Renderer: crate::Renderer { impl<'a, Message, Renderer> From<Checkbox<Message, Renderer>> for Element<'a, Message, Renderer> where - Renderer: 'a + self::Renderer + text::Renderer, + Renderer: 'a + self::Renderer + renderer::Text, Message: 'a, { fn from( diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index ed688a61..761cfcc8 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -366,7 +366,7 @@ where /// able to use a [`PickList`] in your user interface. /// /// [renderer]: crate::renderer -pub trait Renderer: text::Renderer + menu::Renderer { +pub trait Renderer: renderer::Text + menu::Renderer { /// The default padding of a [`PickList`]. const DEFAULT_PADDING: Padding; diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index ba9bd9aa..3d6d22cc 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -5,7 +5,6 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; -use crate::text; use crate::touch; use crate::{ Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point, @@ -39,7 +38,7 @@ use crate::{ /// ///  #[allow(missing_debug_implementations)] -pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> { +pub struct Radio<Message, Renderer: self::Renderer + renderer::Text> { is_selected: bool, on_click: Message, label: String, @@ -52,7 +51,7 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> { style: Renderer::Style, } -impl<Message, Renderer: self::Renderer + text::Renderer> +impl<Message, Renderer: self::Renderer + renderer::Text> Radio<Message, Renderer> where Message: Clone, @@ -135,7 +134,7 @@ where impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer> where Message: Clone, - Renderer: self::Renderer + text::Renderer, + Renderer: self::Renderer + renderer::Text, { fn width(&self) -> Length { self.width @@ -260,7 +259,7 @@ impl<'a, Message, Renderer> From<Radio<Message, Renderer>> for Element<'a, Message, Renderer> where Message: 'a + Clone, - Renderer: 'a + self::Renderer + text::Renderer, + Renderer: 'a + self::Renderer + renderer::Text, { fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> { Element::new(radio) diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index a2438d9c..78bfa4e2 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -24,7 +24,7 @@ use std::hash::Hash; /// ///  #[derive(Debug)] -pub struct Text<Renderer: self::Renderer> { +pub struct Text<Renderer: renderer::Text> { content: String, size: Option<u16>, color: Option<Color>, @@ -35,7 +35,7 @@ pub struct Text<Renderer: self::Renderer> { vertical_alignment: alignment::Vertical, } -impl<Renderer: self::Renderer> Text<Renderer> { +impl<Renderer: renderer::Text> Text<Renderer> { /// Create a new fragment of [`Text`] with the given contents. pub fn new<T: Into<String>>(label: T) -> Self { Text { @@ -103,7 +103,7 @@ impl<Renderer: self::Renderer> Text<Renderer> { impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer> where - Renderer: self::Renderer, + Renderer: renderer::Text, { fn width(&self) -> Length { self.width @@ -176,55 +176,17 @@ where } } -/// The renderer of a [`Text`] fragment. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use [`Text`] in your user interface. -/// -/// [renderer]: crate::Renderer -pub trait Renderer: renderer::Text { - /// Returns the default size of [`Text`]. - fn default_size(&self) -> u16; - - /// Measures the [`Text`] in the given bounds and returns the minimum - /// boundaries that can fit the contents. - fn measure( - &self, - content: &str, - size: u16, - font: Self::Font, - bounds: Size, - ) -> (f32, f32); - - /// Tests whether the provided point is within the boundaries of [`Text`] - /// laid out with the given parameters, returning information about - /// the nearest character. - /// - /// If `nearest_only` is true, the hit test does not consider whether the - /// the point is interior to any glyph bounds, returning only the character - /// with the nearest centeroid. - fn hit_test( - &self, - contents: &str, - size: f32, - font: Self::Font, - bounds: Size, - point: Point, - nearest_only: bool, - ) -> Option<Hit>; -} - impl<'a, Message, Renderer> From<Text<Renderer>> for Element<'a, Message, Renderer> where - Renderer: self::Renderer + 'a, + Renderer: renderer::Text + 'a, { fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> { Element::new(text) } } -impl<Renderer: self::Renderer> Clone for Text<Renderer> { +impl<Renderer: renderer::Text> Clone for Text<Renderer> { fn clone(&self) -> Self { Self { content: self.content.clone(), diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 93af04d1..22cb9092 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -11,20 +11,22 @@ pub use value::Value; use editor::Editor; +use crate::alignment; use crate::event::{self, Event}; use crate::keyboard; use crate::layout; use crate::mouse::{self, click}; use crate::renderer; -use crate::text; use crate::touch; use crate::{ - Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, - Size, Widget, + Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding, + Point, Rectangle, Size, Vector, Widget, }; use std::u32; +pub use iced_style::text_input::{Style, StyleSheet}; + /// A field that can be filled with text. /// /// # Example @@ -50,7 +52,7 @@ use std::u32; /// ``` ///  #[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message, Renderer: self::Renderer> { +pub struct TextInput<'a, Message, Renderer: renderer::Text> { state: &'a mut State, placeholder: String, value: Value, @@ -62,13 +64,13 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> { size: Option<u16>, on_change: Box<dyn Fn(String) -> Message>, on_submit: Option<Message>, - style: Renderer::Style, + style_sheet: &'a dyn StyleSheet, } impl<'a, Message, Renderer> TextInput<'a, Message, Renderer> where Message: Clone, - Renderer: self::Renderer, + Renderer: renderer::Text, { /// Creates a new [`TextInput`]. /// @@ -98,7 +100,7 @@ where size: None, on_change: Box::new(on_change), on_submit: None, - style: Renderer::Style::default(), + style_sheet: Default::default(), } } @@ -148,8 +150,8 @@ where } /// Sets the style of the [`TextInput`]. - pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { - self.style = style.into(); + pub fn style(mut self, style_sheet: &'a dyn StyleSheet) -> Self { + self.style_sheet = style_sheet.into(); self } @@ -163,7 +165,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> for TextInput<'a, Message, Renderer> where Message: Clone, - Renderer: self::Renderer, + Renderer: renderer::Text, { fn width(&self) -> Length { self.width @@ -229,7 +231,8 @@ where self.value.clone() }; - renderer.find_cursor_position( + find_cursor_position( + renderer, text_layout.bounds(), self.font, self.size, @@ -248,16 +251,16 @@ where if self.is_secure { self.state.cursor.select_all(&self.value); } else { - let position = renderer - .find_cursor_position( - text_layout.bounds(), - self.font, - self.size, - &self.value, - &self.state, - target, - ) - .unwrap_or(0); + let position = find_cursor_position( + renderer, + text_layout.bounds(), + self.font, + self.size, + &self.value, + &self.state, + target, + ) + .unwrap_or(0); self.state.cursor.select_range( self.value.previous_start_of_word(position), @@ -296,16 +299,16 @@ where self.value.clone() }; - let position = renderer - .find_cursor_position( - text_layout.bounds(), - self.font, - self.size, - &value, - &self.state, - target, - ) - .unwrap_or(0); + let position = find_cursor_position( + renderer, + text_layout.bounds(), + self.font, + self.size, + &value, + &self.state, + target, + ) + .unwrap_or(0); self.state.cursor.select_range( self.state.cursor.start(&value), @@ -585,39 +588,164 @@ where cursor_position: Point, _viewport: &Rectangle, ) { + let secure_value = self.is_secure.then(|| self.value.secure()); + let value = secure_value.as_ref().unwrap_or(&self.value); + + let bounds = layout.bounds(); + let text_bounds = layout.children().next().unwrap().bounds(); - // TODO - // let value = value.unwrap_or(&self.value); - // let bounds = layout.bounds(); - // let text_bounds = layout.children().next().unwrap().bounds(); - - // if self.is_secure { - // self::Renderer::draw( - // renderer, - // bounds, - // text_bounds, - // cursor_position, - // self.font, - // self.size.unwrap_or(renderer.default_size()), - // &self.placeholder, - // &value.secure(), - // &self.state, - // &self.style, - // ) - // } else { - // self::Renderer::draw( - // renderer, - // bounds, - // text_bounds, - // cursor_position, - // self.font, - // self.size.unwrap_or(renderer.default_size()), - // &self.placeholder, - // value, - // &self.state, - // &self.style, - // ) - // } + let is_mouse_over = bounds.contains(cursor_position); + + let style = if self.state.is_focused() { + self.style_sheet.focused() + } else if is_mouse_over { + self.style_sheet.hovered() + } else { + self.style_sheet.active() + }; + + renderer.fill_rectangle(renderer::Quad { + bounds, + background: style.background, + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }); + + let text = value.to_string(); + let size = self.size.unwrap_or(renderer.default_size()); + + let (cursor, offset) = if self.state.is_focused() { + match self.state.cursor.state(&value) { + cursor::State::Index(position) => { + let (text_value_width, offset) = + measure_cursor_and_scroll_offset( + renderer, + text_bounds, + &value, + size, + position, + self.font, + ); + + ( + Some(renderer::Quad { + bounds: Rectangle { + x: text_bounds.x + text_value_width, + y: text_bounds.y, + width: 1.0, + height: text_bounds.height, + }, + background: Background::Color( + self.style_sheet.value_color(), + ), + border_radius: 0.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }), + offset, + ) + } + cursor::State::Selection { start, end } => { + let left = start.min(end); + let right = end.max(start); + + let (left_position, left_offset) = + measure_cursor_and_scroll_offset( + renderer, + text_bounds, + &value, + size, + left, + self.font, + ); + + let (right_position, right_offset) = + measure_cursor_and_scroll_offset( + renderer, + text_bounds, + &value, + size, + right, + self.font, + ); + + let width = right_position - left_position; + + ( + Some(renderer::Quad { + bounds: Rectangle { + x: text_bounds.x + left_position, + y: text_bounds.y, + width, + height: text_bounds.height, + }, + background: Background::Color( + self.style_sheet.selection_color(), + ), + border_radius: 0.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }), + if end == right { + right_offset + } else { + left_offset + }, + ) + } + } + } else { + (None, 0.0) + }; + + let text_width = renderer.measure_width( + if text.is_empty() { + &self.placeholder + } else { + &text + }, + size, + self.font, + ); + + let render = |renderer: &mut Renderer| { + if let Some(cursor) = cursor { + renderer.fill_rectangle(cursor); + } + + renderer.fill_text(renderer::text::Section { + content: if text.is_empty() { + &self.placeholder + } else { + &text + }, + color: if text.is_empty() { + self.style_sheet.placeholder_color() + } else { + self.style_sheet.value_color() + }, + font: self.font, + bounds: Rectangle { + y: text_bounds.center_y(), + width: f32::INFINITY, + ..text_bounds + }, + size: f32::from(size), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + }); + }; + + if text_width > text_bounds.width { + renderer.with_layer( + text_bounds, + Vector::new(offset as u32, 0), + render, + ); + } else { + render(renderer); + } } fn hash_layout(&self, state: &mut Hasher) { @@ -632,65 +760,11 @@ where } } -/// The renderer of a [`TextInput`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`TextInput`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: text::Renderer + Sized { - /// The style supported by this renderer. - type Style: Default; - - /// Returns the width of the value of the [`TextInput`]. - fn measure_value(&self, value: &str, size: u16, font: Self::Font) -> f32; - - /// Returns the current horizontal offset of the value of the - /// [`TextInput`]. - /// - /// This is the amount of horizontal scrolling applied when the [`Value`] - /// does not fit the [`TextInput`]. - fn offset( - &self, - text_bounds: Rectangle, - font: Self::Font, - size: u16, - value: &Value, - state: &State, - ) -> f32; - - /// Computes the position of the text cursor at the given X coordinate of - /// a [`TextInput`]. - fn find_cursor_position( - &self, - text_bounds: Rectangle, - font: Self::Font, - size: Option<u16>, - value: &Value, - state: &State, - x: f32, - ) -> Option<usize> { - let size = size.unwrap_or(self.default_size()); - - let offset = self.offset(text_bounds, font, size, &value, &state); - - self.hit_test( - &value.to_string(), - size.into(), - font, - Size::INFINITY, - Point::new(x + offset, text_bounds.height / 2.0), - true, - ) - .map(text::Hit::cursor) - } -} - impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Message: 'a + Clone, - Renderer: 'a + self::Renderer, + Renderer: 'a + renderer::Text, { fn from( text_input: TextInput<'a, Message, Renderer>, @@ -781,3 +855,88 @@ mod platform { } } } + +fn offset<Renderer>( + renderer: &Renderer, + text_bounds: Rectangle, + font: Renderer::Font, + size: u16, + value: &Value, + state: &State, +) -> f32 +where + Renderer: renderer::Text, +{ + if state.is_focused() { + let cursor = state.cursor(); + + let focus_position = match cursor.state(value) { + cursor::State::Index(i) => i, + cursor::State::Selection { end, .. } => end, + }; + + let (_, offset) = measure_cursor_and_scroll_offset( + renderer, + text_bounds, + value, + size, + focus_position, + font, + ); + + offset + } else { + 0.0 + } +} + +fn measure_cursor_and_scroll_offset<Renderer>( + renderer: &Renderer, + text_bounds: Rectangle, + value: &Value, + size: u16, + cursor_index: usize, + font: Renderer::Font, +) -> (f32, f32) +where + Renderer: renderer::Text, +{ + let text_before_cursor = value.until(cursor_index).to_string(); + + let text_value_width = + renderer.measure_width(&text_before_cursor, size, font); + + let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); + + (text_value_width, offset) +} + +/// Computes the position of the text cursor at the given X coordinate of +/// a [`TextInput`]. +fn find_cursor_position<Renderer>( + renderer: &Renderer, + text_bounds: Rectangle, + font: Renderer::Font, + size: Option<u16>, + value: &Value, + state: &State, + x: f32, +) -> Option<usize> +where + Renderer: renderer::Text, +{ + let size = size.unwrap_or(renderer.default_size()); + + let offset = offset(renderer, text_bounds, font, size, &value, &state); + + renderer + .hit_test( + &value.to_string(), + size.into(), + font, + Size::INFINITY, + Point::new(x + offset, text_bounds.height / 2.0), + true, + ) + .map(renderer::text::Hit::cursor) +} diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 6c7fa97b..46a9850b 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -6,7 +6,6 @@ use crate::event; use crate::layout; use crate::mouse; use crate::renderer; -use crate::text; use crate::{ Alignment, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Row, Text, Widget, @@ -28,7 +27,7 @@ use crate::{ /// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b)); /// ``` #[allow(missing_debug_implementations)] -pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> { +pub struct Toggler<Message, Renderer: self::Renderer + renderer::Text> { is_active: bool, on_toggle: Box<dyn Fn(bool) -> Message>, label: Option<String>, @@ -41,7 +40,7 @@ pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> { style: Renderer::Style, } -impl<Message, Renderer: self::Renderer + text::Renderer> +impl<Message, Renderer: self::Renderer + renderer::Text> Toggler<Message, Renderer> { /// Creates a new [`Toggler`]. @@ -119,7 +118,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer> impl<Message, Renderer> Widget<Message, Renderer> for Toggler<Message, Renderer> where - Renderer: self::Renderer + text::Renderer, + Renderer: self::Renderer + renderer::Text, { fn width(&self) -> Length { self.width @@ -246,7 +245,7 @@ pub trait Renderer: crate::Renderer { impl<'a, Message, Renderer> From<Toggler<Message, Renderer>> for Element<'a, Message, Renderer> where - Renderer: 'a + self::Renderer + text::Renderer, + Renderer: 'a + self::Renderer + renderer::Text, Message: 'a, { fn from( diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 4e8483ad..c2bc8af0 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -7,7 +7,7 @@ use crate::event; use crate::layout; use crate::renderer; use crate::widget::container; -use crate::widget::text::{self, Text}; +use crate::widget::text::Text; use crate::{Clipboard, Element, Event, Hasher, Layout, Length, Point, Widget}; /// An element to display a widget over another. @@ -160,7 +160,7 @@ where /// /// [`Tooltip`]: struct.Tooltip.html /// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer + text::Renderer { +pub trait Renderer: renderer::Text { /// The default padding of a [`Tooltip`] drawn by this renderer. const DEFAULT_PADDING: u16; } diff --git a/style/src/text_input.rs b/style/src/text_input.rs index 19acea65..13b64cd5 100644 --- a/style/src/text_input.rs +++ b/style/src/text_input.rs @@ -73,17 +73,8 @@ impl StyleSheet for Default { } } -impl std::default::Default for Box<dyn StyleSheet> { +impl std::default::Default for &'static dyn StyleSheet { fn default() -> Self { - Box::new(Default) - } -} - -impl<T> From<T> for Box<dyn StyleSheet> -where - T: 'static + StyleSheet, -{ - fn from(style: T) -> Self { - Box::new(style) + &Default } } diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs index e4877f2a..99f765bb 100644 --- a/web/src/widget/text_input.rs +++ b/web/src/widget/text_input.rs @@ -39,7 +39,7 @@ pub struct TextInput<'a, Message> { size: Option<u16>, on_change: Rc<Box<dyn Fn(String) -> Message>>, on_submit: Option<Message>, - style_sheet: Box<dyn StyleSheet>, + style_sheet: &'a dyn StyleSheet, } impl<'a, Message> TextInput<'a, Message> { @@ -112,8 +112,8 @@ impl<'a, Message> TextInput<'a, Message> { } /// Sets the style of the [`TextInput`]. - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { - self.style_sheet = style.into(); + pub fn style(mut self, style_sheet: &'a dyn StyleSheet) -> Self { + self.style_sheet = style_sheet; self } } |