From 0ab92590dd4ead9a41d89f48d5094998c1d4878c Mon Sep 17 00:00:00 2001 From: Ultraxime Date: Fri, 19 Jul 2024 20:37:39 +0200 Subject: [PATCH 1/4] Creating typed_input (a text input but sending message only when it is valid for the given type) Adding suport for dots in number input --- Cargo.lock | 8 + Cargo.toml | 4 +- examples/number_input/src/main.rs | 3 +- examples/typed_input/Cargo.toml | 15 ++ examples/typed_input/src/main.rs | 55 ++++++ src/widgets.rs | 3 + src/widgets/number_input.rs | 95 +++++---- src/widgets/typed_input.rs | 313 ++++++++++++++++++++++++++++++ 8 files changed, 451 insertions(+), 45 deletions(-) create mode 100644 examples/typed_input/Cargo.toml create mode 100644 examples/typed_input/src/main.rs create mode 100644 src/widgets/typed_input.rs diff --git a/Cargo.lock b/Cargo.lock index ced690e1..d0ba14bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3566,6 +3566,14 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" +[[package]] +name = "typed_input" +version = "0.1.0" +dependencies = [ + "iced", + "iced_aw", +] + [[package]] name = "typenum" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 7b7ff67b..f328d9fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,8 @@ tab_bar = [] tabs = ["tab_bar"] time_picker = ["chrono", "icons", "iced/canvas"] wrap = [] -number_input = ["num-format", "num-traits"] +number_input = ["num-format", "num-traits", "typed_input"] +typed_input = [] selection_list = [] menu = [] quad = [] @@ -80,6 +81,7 @@ members = [ "examples/badge", "examples/card", "examples/number_input", + "examples/typed_input", "examples/date_picker", "examples/color_picker", "examples/grid", diff --git a/examples/number_input/src/main.rs b/examples/number_input/src/main.rs index bea2412a..b92269b6 100644 --- a/examples/number_input/src/main.rs +++ b/examples/number_input/src/main.rs @@ -31,12 +31,13 @@ fn main() -> iced::Result { impl NumberInputDemo { fn update(&mut self, message: self::Message) { let Message::NumInpChanged(val) = message; + println!("Value changed to {:?}", val); self.value = val; } fn view(&self) -> Element { let lb_minute = Text::new("Number Input:"); - let txt_minute = number_input(self.value, 0.0..250.0, Message::NumInpChanged) + let txt_minute = number_input(self.value, -10.0..250.0, Message::NumInpChanged) .style(number_input::number_input::primary) .step(0.5); diff --git a/examples/typed_input/Cargo.toml b/examples/typed_input/Cargo.toml new file mode 100644 index 00000000..37a785cd --- /dev/null +++ b/examples/typed_input/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "typed_input" +version = "0.1.0" +authors = ["Ultraxime <36888699+Ultraxime@users.noreply.github.com>"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced_aw = { workspace = true, features = [ + "typed_input", + "icons", +] } + +iced.workspace=true diff --git a/examples/typed_input/src/main.rs b/examples/typed_input/src/main.rs new file mode 100644 index 00000000..aa67b22a --- /dev/null +++ b/examples/typed_input/src/main.rs @@ -0,0 +1,55 @@ +use iced::{ + widget::{Container, Row, Text}, + Alignment, Element, Length, +}; +use iced_aw::widgets::typed_input; + +#[derive(Default, Debug)] +pub struct TypedInputDemo { + value: f32, +} + +#[derive(Debug, Clone)] +pub enum Message { + TypedInpChanged(f32), +} + +fn main() -> iced::Result { + iced::application( + "Typed Input example", + TypedInputDemo::update, + TypedInputDemo::view, + ) + .window_size(iced::Size { + width: 250.0, + height: 200.0, + }) + .font(iced_aw::BOOTSTRAP_FONT_BYTES) + .run() +} + +impl TypedInputDemo { + fn update(&mut self, message: self::Message) { + let Message::TypedInpChanged(val) = message; + println!("Value changed to {:?}", val); + self.value = val; + } + + fn view(&self) -> Element { + let lb_minute = Text::new("Typed Input:"); + let txt_minute = typed_input::TypedInput::new(self.value, Message::TypedInpChanged); + + Container::new( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(lb_minute) + .push(txt_minute), + ) + .width(Length::Fill) + .height(Length::Fill) + .center_x(Length::Fill) + .center_y(Length::Fill) + .into() + } +} diff --git a/src/widgets.rs b/src/widgets.rs index d5281a03..e8deeea3 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -23,6 +23,9 @@ pub mod number_input; pub type NumberInput<'a, T, Message, Theme, Renderer> = number_input::NumberInput<'a, T, Message, Theme, Renderer>; +#[cfg(feature = "typed_input")] +pub mod typed_input; + #[cfg(feature = "card")] pub mod card; #[cfg(feature = "card")] diff --git a/src/widgets/number_input.rs b/src/widgets/number_input.rs index b0e41c11..2a202f5f 100644 --- a/src/widgets/number_input.rs +++ b/src/widgets/number_input.rs @@ -17,7 +17,7 @@ use iced::{ widget::{ text::LineHeight, text_input::{self, cursor, Value}, - Column, Container, Row, Text, TextInput, + Column, Container, Row, Text, }, Alignment, Background, Border, Color, Element, Event, Length, Padding, Pixels, Point, Rectangle, Shadow, Size, @@ -37,6 +37,7 @@ pub use crate::{ StyleFn, }, }; +use crate::widgets::typed_input::TypedInput; /// The default padding const DEFAULT_PADDING: f32 = 5.0; @@ -81,7 +82,7 @@ where /// The text size of the [`NumberInput`]. size: Option, /// The underlying element of the [`NumberInput`]. - content: TextInput<'a, Message, Theme, Renderer>, + content: TypedInput<'a, T, Message, Theme, Renderer>, /// The ``on_change`` event of the [`NumberInput`]. on_change: Box Message>, /// The style of the [`NumberInput`]. @@ -116,9 +117,6 @@ where T: 'static, { let padding = DEFAULT_PADDING; - let convert_to_num = move |s: String| { - on_changed(T::from_str(&s).unwrap_or(if s.is_empty() { T::zero() } else { value })) - }; Self { value, @@ -127,8 +125,7 @@ where max: Self::set_max(bounds.end_bound()), padding, size: None, - content: TextInput::new("", format!("{value}").as_str()) - .on_input(convert_to_num) + content: TypedInput::new(value, on_changed) .padding(padding) .width(Length::Fixed(127.0)) .class(Theme::default_input()), @@ -346,7 +343,7 @@ where .shrink(padding); let content = self .content - .layout(&mut tree.children[0], renderer, &limits, None); + .layout(&mut tree.children[0], renderer, &limits); let limits2 = Limits::new(Size::new(0.0, 0.0), content.size()); let txt_size = self.size.unwrap_or_else(|| renderer.default_size().0); @@ -456,10 +453,15 @@ where let child = state.children.get_mut(0).expect("fail to get child"); let text_input = child + .children + .get_mut(0) + .expect("fail to get text input") .state .downcast_mut::>(); let modifiers = state.state.downcast_mut::(); + let current_text = self.content.text().to_string(); + let mut forward_to_text = |event, shell, child, clipboard| { self.content.on_event( child, event, content, cursor, renderer, clipboard, shell, viewport, @@ -485,39 +487,40 @@ where return forward_to_text(event, shell, child, clipboard); } else if text == "\u{8}" { // Backspace - if !T::zero().eq(&self.value) { - let mut new_val = self.value.to_string(); - match text_input.cursor().state(&Value::new(&new_val)) { - cursor::State::Index(idx) - if idx >= 1 && idx <= new_val.len() => - { - _ = new_val.remove(idx - 1); - } - cursor::State::Selection { start, end } - if start <= new_val.len() && end <= new_val.len() => - { - new_val.replace_range(start.min(end)..start.max(end), ""); - } - _ => return event::Status::Ignored, + let mut new_val = current_text; + match text_input.cursor().state(&Value::new(&new_val)) { + cursor::State::Index(idx) + if idx >= 1 && idx <= new_val.len() => + { + _ = new_val.remove(idx - 1); } - - if new_val.is_empty() { - new_val = T::zero().to_string(); + cursor::State::Selection { start, end } + if start <= new_val.len() && end <= new_val.len() => + { + new_val.replace_range(start.min(end)..start.max(end), ""); } + _ => return event::Status::Ignored, + } + + if new_val.is_empty() { + new_val = T::zero().to_string(); + } - match T::from_str(&new_val) { - Ok(val) - if (self.min..self.max).contains(&val) - && val != self.value => - { - self.value = val; - forward_to_text(event, shell, child, clipboard) - } - Ok(_) => event::Status::Captured, - _ => event::Status::Ignored, + match T::from_str(&new_val) { + Ok(val) + if (self.min..self.max).contains(&val) + && val != self.value => + { + self.value = val; + forward_to_text(event, shell, child, clipboard) } - } else { - event::Status::Ignored + Ok(val) + if (self.min..self.max).contains(&val) => + { + forward_to_text(event, shell, child, clipboard) + } + Ok(_) => event::Status::Captured, + _ => event::Status::Ignored, } } else { let input = if text == "\u{16}" { @@ -526,7 +529,7 @@ where Some(paste) => paste, None => return event::Status::Ignored, } - } else if text.parse::().is_err() && text != "-" { + } else if text.parse::().is_err() && text != "-" && text != "." { return event::Status::Ignored; } else { text.to_string() @@ -534,7 +537,7 @@ where let input = input.trim(); - let mut new_val = self.value.to_string(); + let mut new_val = current_text; match text_input.cursor().state(&Value::new(&new_val)) { cursor::State::Index(idx) if idx <= new_val.len() => { new_val.insert_str(idx, input); @@ -554,7 +557,11 @@ where self.value = val; forward_to_text(event, shell, child, clipboard) } - Ok(_) => event::Status::Captured, + Ok(val) + if (self.min..self.max).contains(&val) => + forward_to_text(event, shell, child, clipboard), + Ok(_) => + event::Status::Captured, _ => event::Status::Ignored, } } @@ -569,7 +576,9 @@ where event::Status::Captured } keyboard::Key::Named( - keyboard::key::Named::ArrowLeft | keyboard::key::Named::ArrowRight, + keyboard::key::Named::ArrowLeft | keyboard::key::Named::ArrowRight | + keyboard::key::Named::Home | + keyboard::key::Named::End, ) => forward_to_text(event, shell, child, clipboard), _ => event::Status::Ignored, }, @@ -661,7 +670,7 @@ where state: &Tree, renderer: &mut Renderer, theme: &Theme, - _style: &renderer::Style, + style: &renderer::Style, layout: Layout<'_>, cursor: Cursor, viewport: &Rectangle, @@ -684,9 +693,9 @@ where &state.children[0], renderer, theme, + style, content_layout, cursor, - None, viewport, ); let is_decrease_disabled = self.value <= self.min || self.min == self.max; diff --git a/src/widgets/typed_input.rs b/src/widgets/typed_input.rs new file mode 100644 index 00000000..570c7b08 --- /dev/null +++ b/src/widgets/typed_input.rs @@ -0,0 +1,313 @@ +//! Display fields that can only be filled with a specific type. +//! +//! *This API requires the following crate features to be activated: `typed_input`* + +use iced::mouse::{self, Cursor}; +use iced::{widget::text_input::{self, TextInput}, Size, Event, event}; +use iced::advanced::layout::{Node, Limits, Layout}; +use iced::advanced::widget::{Tree, Widget, tree::{State, Tag}, Operation}; +use iced::advanced::{Clipboard, Shell}; +use iced::{Length, Rectangle, Element}; + +use std::{ + fmt::Display, + str::FromStr, +}; + +/// The default padding +const DEFAULT_PADDING: f32 = 5.0; + +/// A field that can only be filled with a specific type. +/// +/// # Example +/// ```ignore +/// # use iced_aw::TypedInput; +/// # +/// #[derive(Debug, Clone)] +/// enum Message { +/// TypedInputChanged(u32), +/// } +/// +/// let value = 12; +/// let max = 1275; +/// +/// let input = TypedInput::new( +/// value, +/// Message::TypedInputChanged, +/// ); +/// ``` +pub struct TypedInput<'a, T, Message, Theme = iced::Theme, Renderer = iced::Renderer> +where + Renderer: iced::advanced::text::Renderer, + Theme: text_input::Catalog, +{ + /// The current value of the [`TypedInput`]. + value: T, + /// The underlying element of the [`TypeInput`]. + text_input: text_input::TextInput<'a, InternalMessage, Theme, Renderer>, + text: String, + /// The ``on_change`` event of the [`TextInput`]. + on_change: Box Message>, + /// The ``on_change`` event of the [`TextInput`]. + on_submit: Option, + /// The font text of the [`TextInput`]. + font: Renderer::Font, +} + +#[derive(Debug, Clone, PartialEq)] +enum InternalMessage { + OnChange(String), + OnSubmit, +} + +impl<'a, T, Message, Theme, Renderer> TypedInput<'a, T, Message, Theme, Renderer> +where + T: Display + FromStr, + Message: Clone, + Renderer: iced::advanced::text::Renderer, + Theme: text_input::Catalog, +{ + /// Creates a new [`TypedInput`]. + /// + /// It expects: + /// - the current value + /// - a function that produces a message when the [`TypedInput`] changes + pub fn new(value: T, on_changed: F) -> Self + where + F: 'static + Fn(T) -> Message + Copy, + T: 'a + Clone, + { + let padding = DEFAULT_PADDING; + // let move_value = value.clone(); + // let convert_to_t = move |s: String| on_changed(T::from_str(&s).unwrap_or(move_value.clone())); + + Self { + value: value.clone(), + text_input: text_input::TextInput::new("", format!("{value}").as_str()) + .on_input(InternalMessage::OnChange) + .on_submit(InternalMessage::OnSubmit) + .padding(padding) + .width(Length::Fixed(127.0)) + .class(::default()), + text: value.to_string(), + on_change: Box::new(on_changed), + on_submit: None, + font: Renderer::Font::default(), + } + } + + /// Gets the text value of the [`TypedInput`]. + pub fn text(&self) -> &str { + &self.text + } + + /// Sets the width of the [`TypedInput`]. + #[must_use] + pub fn width(mut self, width: Length) -> Self { + self.text_input = self.text_input.width(width); + self + } + + /// Sets the [`Font`] of the [`Text`]. + /// + /// [`Font`]: core::Font + /// [`Text`]: core::widget::Text + #[allow(clippy::needless_pass_by_value)] + #[must_use] + pub fn font(mut self, font: Renderer::Font) -> Self { + self.font = font; + self.text_input = self.text_input.font(font); + self + } + + /// Sets the message that should be produced when the [`TextInput`] is + /// focused and the enter key is pressed. + #[must_use] + pub fn on_submit(mut self, message: Message) -> Self { + self.on_submit = Some(message); + self + } + + /// Sets the padding of the [`TypedInput`]. + #[must_use] + pub fn padding(mut self, units: f32) -> Self { + self.text_input = self.text_input.padding(units); + self + } + + /// Sets the text size of the [`TypedInput`]. + #[must_use] + pub fn size(mut self, size: f32) -> Self { + self.text_input = self.text_input.size(size); + self + } + + /// Sets the style of the input of the [`TypedInput`]. + #[must_use] + pub fn style( + mut self, + style: impl Fn(&Theme, text_input::Status) -> text_input::Style + 'a, + ) -> Self + where + ::Class<'a>: From>, + { + self.text_input = self.text_input.style(style); + self + } + + /// Sets the class of the input of the [`TypedInput`]. + #[must_use] + pub fn class( + mut self, + class: impl Into<::Class<'a>>, + ) -> Self + { + self.text_input = self.text_input.class(class); + self + } +} + +impl<'a, T, Message, Theme, Renderer> Widget + for TypedInput<'a, T, Message, Theme, Renderer> +where + T: Display + FromStr + Clone + PartialEq, + Message: 'a + Clone, + Renderer: 'a + iced::advanced::text::Renderer, + Theme: text_input::Catalog, +{ + fn tag(&self) -> Tag { + Tag::of::<()>() + } + fn state(&self) -> State { + State::new(()) + } + + fn children(&self) -> Vec { + vec![Tree { + tag: self.text_input.tag(), + state: self.text_input.state(), + children: self.text_input.children(), + }] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children_custom( + &[&self.text_input], + |state, content| content.diff(state), + |&content| Tree { + tag: content.tag(), + state: content.state(), + children: content.children(), + }, + ); + } + + fn size(&self) -> Size { + as Widget<_, _, _>>::size(&self.text_input) + } + + fn layout(&self, tree: &mut Tree,renderer: &Renderer, limits: &Limits) -> Node { + let content = as Widget<_, _, _>>::layout(&self.text_input, &mut tree.children[0], renderer, limits); + let size = limits.resolve(Length::Shrink, Length::Shrink, content.size()); + Node::with_children( + size, + vec![ content ] + ) + } + + fn draw(&self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &iced::advanced::renderer::Style, layout: Layout<'_>, cursor: Cursor, viewport: &Rectangle) { + let mut children = layout.children(); + let text_input_layout = children.next().expect("fail to get TextInput layout"); + as Widget<_, _, _>>::draw(&self.text_input, &tree.children[0], renderer, theme, style, text_input_layout, cursor, viewport); + } + + fn mouse_interaction( + &self, + state: &Tree, + layout: Layout<'_>, + cursor: Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + as Widget<_, _, _>>::mouse_interaction(&self.text_input, &state.children[0], layout.children().next().expect("TypedInput inner child Textbox was not created."), cursor, viewport, renderer) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation<()>, + ) { + as Widget<_, _, _>>::operate(&self.text_input, &mut tree.children[0], layout.children().next().expect("TypedInput inner child Textbox was not created."), renderer, operation) + } + + #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] + fn on_event( + &mut self, + state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell, + viewport: &Rectangle, + ) -> event::Status { + let text_input_layout = layout.children().next().expect("fail to get text_input layout"); + + let child = &mut state.children[0]; + + let mut messages = Vec::new(); + let mut sub_shell = Shell::new(&mut messages); + let status = self.text_input.on_event( + child, event, text_input_layout, cursor, renderer, clipboard, &mut sub_shell, viewport, + ); + // todo!() + // println!("shell: {:?}", shell); + if let Some(redraw) = sub_shell.redraw_request() { + shell.request_redraw(redraw); + } + if sub_shell.is_layout_invalid() { + shell.invalidate_layout(); + } + if sub_shell.are_widgets_invalid() { + shell.invalidate_widgets(); + } + + for message in messages { + match message { + InternalMessage::OnChange(value) => { + self.text = value; + if let Ok(val) = T::from_str(&self.text) { + if self.value != val { + self.value = val.clone(); + shell.publish((self.on_change)(val)); + } + } + shell.invalidate_layout(); + } + InternalMessage::OnSubmit => { + if let Some(on_submit) = &self.on_submit { + shell.publish(on_submit.clone()); + } + } + } + } + status + } + +} + +impl<'a, T, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + T: 'a + Display + FromStr + Clone + PartialEq, + Message: 'a + Clone, + Renderer: 'a + iced::advanced::text::Renderer, + Theme: 'a + text_input::Catalog, +{ + fn from(typed_input: TypedInput<'a, T, Message, Theme, Renderer>) -> Self { + Element::new(typed_input) + } +} From 000d93e484f4ec9aef8d593e206952bf533336cd Mon Sep 17 00:00:00 2001 From: Ultraxime Date: Fri, 19 Jul 2024 23:18:24 +0200 Subject: [PATCH 2/4] Making typedInput transparent (behaving the same way than the textInput beneath) --- examples/typed_input/src/main.rs | 4 +- src/widgets/number_input.rs | 40 ++++------ src/widgets/typed_input.rs | 133 +++++++++++++++++-------------- 3 files changed, 90 insertions(+), 87 deletions(-) diff --git a/examples/typed_input/src/main.rs b/examples/typed_input/src/main.rs index aa67b22a..6b261ad0 100644 --- a/examples/typed_input/src/main.rs +++ b/examples/typed_input/src/main.rs @@ -37,12 +37,12 @@ impl TypedInputDemo { fn view(&self) -> Element { let lb_minute = Text::new("Typed Input:"); - let txt_minute = typed_input::TypedInput::new(self.value, Message::TypedInpChanged); + let txt_minute = typed_input::TypedInput::new(&self.value, Message::TypedInpChanged); Container::new( Row::new() .spacing(10) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .push(lb_minute) .push(txt_minute), ) diff --git a/src/widgets/number_input.rs b/src/widgets/number_input.rs index 0428c7d3..78a79da8 100644 --- a/src/widgets/number_input.rs +++ b/src/widgets/number_input.rs @@ -30,6 +30,7 @@ use std::{ }; use crate::style::{self, Status}; +use crate::widgets::typed_input::TypedInput; pub use crate::{ core::icons::{bootstrap::icon_to_string, Bootstrap, BOOTSTRAP_FONT}, style::{ @@ -37,7 +38,6 @@ pub use crate::{ StyleFn, }, }; -use crate::widgets::typed_input::TypedInput; /// The default padding const DEFAULT_PADDING: f32 = 5.0; @@ -125,7 +125,7 @@ where max: Self::set_max(bounds.end_bound()), padding, size: None, - content: TypedInput::new(value, on_changed) + content: TypedInput::new(&value, on_changed) .padding(padding) .width(Length::Fixed(127.0)) .class(Theme::default_input()), @@ -453,14 +453,11 @@ where let child = state.children.get_mut(0).expect("fail to get child"); let text_input = child - .children - .get_mut(0) - .expect("fail to get text input") .state .downcast_mut::>(); let modifiers = state.state.downcast_mut::(); - let current_text = self.content.text().to_string(); + let current_text = self.content.text().to_owned(); let mut forward_to_text = |event, shell, child, clipboard| { self.content.on_event( @@ -492,9 +489,7 @@ where } let mut new_val = current_text; match text_input.cursor().state(&Value::new(&new_val)) { - cursor::State::Index(idx) - if idx >= 1 && idx <= new_val.len() => - { + cursor::State::Index(idx) if idx >= 1 && idx <= new_val.len() => { _ = new_val.remove(idx - 1); } cursor::State::Selection { start, end } @@ -511,17 +506,14 @@ where match T::from_str(&new_val) { Ok(val) - if (self.min..self.max).contains(&val) - && val != self.value => + if (self.min..self.max).contains(&val) && val != self.value => { self.value = val; forward_to_text(event, shell, child, clipboard) } - Ok(val) - if (self.min..self.max).contains(&val) => - { - forward_to_text(event, shell, child, clipboard) - } + Ok(val) if (self.min..self.max).contains(&val) => { + forward_to_text(event, shell, child, clipboard) + } Ok(_) => event::Status::Captured, _ => event::Status::Ignored, } @@ -560,11 +552,10 @@ where self.value = val; forward_to_text(event, shell, child, clipboard) } - Ok(val) - if (self.min..self.max).contains(&val) => - forward_to_text(event, shell, child, clipboard), - Ok(_) => - event::Status::Captured, + Ok(val) if (self.min..self.max).contains(&val) => { + forward_to_text(event, shell, child, clipboard) + } + Ok(_) => event::Status::Captured, _ => event::Status::Ignored, } } @@ -579,9 +570,10 @@ where event::Status::Captured } keyboard::Key::Named( - keyboard::key::Named::ArrowLeft | keyboard::key::Named::ArrowRight | - keyboard::key::Named::Home | - keyboard::key::Named::End, + keyboard::key::Named::ArrowLeft + | keyboard::key::Named::ArrowRight + | keyboard::key::Named::Home + | keyboard::key::Named::End, ) => forward_to_text(event, shell, child, clipboard), _ => event::Status::Ignored, }, diff --git a/src/widgets/typed_input.rs b/src/widgets/typed_input.rs index 570c7b08..75047809 100644 --- a/src/widgets/typed_input.rs +++ b/src/widgets/typed_input.rs @@ -2,17 +2,21 @@ //! //! *This API requires the following crate features to be activated: `typed_input`* -use iced::mouse::{self, Cursor}; -use iced::{widget::text_input::{self, TextInput}, Size, Event, event}; -use iced::advanced::layout::{Node, Limits, Layout}; -use iced::advanced::widget::{Tree, Widget, tree::{State, Tag}, Operation}; +use iced::advanced::layout::{Layout, Limits, Node}; +use iced::advanced::widget::{ + tree::{State, Tag}, + Operation, Tree, Widget, +}; use iced::advanced::{Clipboard, Shell}; -use iced::{Length, Rectangle, Element}; - -use std::{ - fmt::Display, - str::FromStr, +use iced::mouse::{self, Cursor}; +use iced::{ + event, + widget::text_input::{self, TextInput}, + Event, Size, }; +use iced::{Element, Length, Rectangle}; + +use std::{fmt::Display, str::FromStr}; /// The default padding const DEFAULT_PADDING: f32 = 5.0; @@ -72,26 +76,24 @@ where /// It expects: /// - the current value /// - a function that produces a message when the [`TypedInput`] changes - pub fn new(value: T, on_changed: F) -> Self + pub fn new(value: &T, on_changed: F) -> Self where F: 'static + Fn(T) -> Message + Copy, T: 'a + Clone, { let padding = DEFAULT_PADDING; - // let move_value = value.clone(); - // let convert_to_t = move |s: String| on_changed(T::from_str(&s).unwrap_or(move_value.clone())); - Self { - value: value.clone(), + Self { + value: value.clone(), text_input: text_input::TextInput::new("", format!("{value}").as_str()) .on_input(InternalMessage::OnChange) .on_submit(InternalMessage::OnSubmit) .padding(padding) .width(Length::Fixed(127.0)) - .class(::default()), + .class(::default()), text: value.to_string(), on_change: Box::new(on_changed), - on_submit: None, + on_submit: None, font: Renderer::Font::default(), } } @@ -157,11 +159,7 @@ where /// Sets the class of the input of the [`TypedInput`]. #[must_use] - pub fn class( - mut self, - class: impl Into<::Class<'a>>, - ) -> Self - { + pub fn class(mut self, class: impl Into<::Class<'a>>) -> Self { self.text_input = self.text_input.class(class); self } @@ -176,49 +174,48 @@ where Theme: text_input::Catalog, { fn tag(&self) -> Tag { - Tag::of::<()>() + as Widget<_, _, _>>::tag(&self.text_input) } fn state(&self) -> State { - State::new(()) + as Widget<_, _, _>>::state(&self.text_input) } fn children(&self) -> Vec { - vec![Tree { - tag: self.text_input.tag(), - state: self.text_input.state(), - children: self.text_input.children(), - }] + as Widget<_, _, _>>::children(&self.text_input) } - fn diff(&self, tree: &mut Tree) { - tree.diff_children_custom( - &[&self.text_input], - |state, content| content.diff(state), - |&content| Tree { - tag: content.tag(), - state: content.state(), - children: content.children(), - }, - ); + fn diff(&self, state: &mut Tree) { + as Widget<_, _, _>>::diff(&self.text_input, state); } fn size(&self) -> Size { as Widget<_, _, _>>::size(&self.text_input) } - fn layout(&self, tree: &mut Tree,renderer: &Renderer, limits: &Limits) -> Node { - let content = as Widget<_, _, _>>::layout(&self.text_input, &mut tree.children[0], renderer, limits); - let size = limits.resolve(Length::Shrink, Length::Shrink, content.size()); - Node::with_children( - size, - vec![ content ] - ) + fn layout(&self, state: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + as Widget<_, _, _>>::layout(&self.text_input, state, renderer, limits) } - fn draw(&self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &iced::advanced::renderer::Style, layout: Layout<'_>, cursor: Cursor, viewport: &Rectangle) { - let mut children = layout.children(); - let text_input_layout = children.next().expect("fail to get TextInput layout"); - as Widget<_, _, _>>::draw(&self.text_input, &tree.children[0], renderer, theme, style, text_input_layout, cursor, viewport); + fn draw( + &self, + state: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &iced::advanced::renderer::Style, + layout: Layout<'_>, + cursor: Cursor, + viewport: &Rectangle, + ) { + as Widget<_, _, _>>::draw( + &self.text_input, + state, + renderer, + theme, + style, + layout, + cursor, + viewport, + ); } fn mouse_interaction( @@ -229,17 +226,30 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - as Widget<_, _, _>>::mouse_interaction(&self.text_input, &state.children[0], layout.children().next().expect("TypedInput inner child Textbox was not created."), cursor, viewport, renderer) + as Widget<_, _, _>>::mouse_interaction( + &self.text_input, + state, + layout, + cursor, + viewport, + renderer, + ) } fn operate( &self, - tree: &mut Tree, + state: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation<()>, ) { - as Widget<_, _, _>>::operate(&self.text_input, &mut tree.children[0], layout.children().next().expect("TypedInput inner child Textbox was not created."), renderer, operation) + as Widget<_, _, _>>::operate( + &self.text_input, + state, + layout, + renderer, + operation, + ); } #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] @@ -254,17 +264,19 @@ where shell: &mut Shell, viewport: &Rectangle, ) -> event::Status { - let text_input_layout = layout.children().next().expect("fail to get text_input layout"); - - let child = &mut state.children[0]; - let mut messages = Vec::new(); let mut sub_shell = Shell::new(&mut messages); let status = self.text_input.on_event( - child, event, text_input_layout, cursor, renderer, clipboard, &mut sub_shell, viewport, - ); - // todo!() - // println!("shell: {:?}", shell); + state, + event, + layout, + cursor, + renderer, + clipboard, + &mut sub_shell, + viewport, + ); + if let Some(redraw) = sub_shell.redraw_request() { shell.request_redraw(redraw); } @@ -296,7 +308,6 @@ where } status } - } impl<'a, T, Message, Theme, Renderer> From> From f9490f2331e41ebef5e1f3fed461ff0621ababa1 Mon Sep 17 00:00:00 2001 From: Ultraxime Date: Tue, 23 Jul 2024 21:40:13 +0200 Subject: [PATCH 3/4] Correcting the non inclusion of the uper bound in number input --- src/widgets/number_input.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/widgets/number_input.rs b/src/widgets/number_input.rs index 78a79da8..c6d9fb1f 100644 --- a/src/widgets/number_input.rs +++ b/src/widgets/number_input.rs @@ -503,15 +503,15 @@ where if new_val.is_empty() { new_val = T::zero().to_string(); } - + match T::from_str(&new_val) { Ok(val) - if (self.min..self.max).contains(&val) && val != self.value => + if val >= self.min && val <= self.max && val != self.value => { self.value = val; forward_to_text(event, shell, child, clipboard) } - Ok(val) if (self.min..self.max).contains(&val) => { + Ok(val) if val >= self.min && val <= self.max => { forward_to_text(event, shell, child, clipboard) } Ok(_) => event::Status::Captured, @@ -547,12 +547,12 @@ where match T::from_str(&new_val) { Ok(val) - if (self.min..self.max).contains(&val) && val != self.value => + if val >= self.min && val <= self.max && val != self.value => { self.value = val; forward_to_text(event, shell, child, clipboard) } - Ok(val) if (self.min..self.max).contains(&val) => { + Ok(val) if val >= self.min && val <= self.max => { forward_to_text(event, shell, child, clipboard) } Ok(_) => event::Status::Captured, From 02ac8b51114c5996d7f695117ebd130556cc5fa2 Mon Sep 17 00:00:00 2001 From: Ultraxime Date: Tue, 23 Jul 2024 22:08:17 +0200 Subject: [PATCH 4/4] Correcting fmt --- src/widgets/number_input.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/number_input.rs b/src/widgets/number_input.rs index c6d9fb1f..b720a4cd 100644 --- a/src/widgets/number_input.rs +++ b/src/widgets/number_input.rs @@ -503,10 +503,10 @@ where if new_val.is_empty() { new_val = T::zero().to_string(); } - + match T::from_str(&new_val) { Ok(val) - if val >= self.min && val <= self.max && val != self.value => + if val >= self.min && val <= self.max && val != self.value => { self.value = val; forward_to_text(event, shell, child, clipboard)