diff options
Diffstat (limited to 'widget/src')
40 files changed, 1729 insertions, 559 deletions
diff --git a/widget/src/button.rs b/widget/src/button.rs index 384a3156..0ebb8dcc 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -10,8 +10,8 @@ use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; use crate::core::{ - Background, Clipboard, Color, Element, Layout, Length, Padding, Point, - Rectangle, Shell, Vector, Widget, + Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle, + Shell, Size, Vector, Widget, }; pub use iced_style::button::{Appearance, StyleSheet}; @@ -71,11 +71,14 @@ where { /// Creates a new [`Button`] with the given content. pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self { + let content = content.into(); + let size = content.as_widget().size_hint(); + Button { - content: content.into(), + content, on_press: None, - width: Length::Shrink, - height: Length::Shrink, + width: size.width.fluid(), + height: size.height.fluid(), padding: Padding::new(5.0), style: <Renderer::Theme as StyleSheet>::Style::default(), } @@ -149,12 +152,11 @@ where tree.diff_children(std::slice::from_ref(&self.content)); } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -431,15 +433,7 @@ pub fn layout( padding: Padding, layout_content: impl FnOnce(&layout::Limits) -> layout::Node, ) -> layout::Node { - let limits = limits.width(width).height(height); - - let mut content = layout_content(&limits.pad(padding)); - let padding = padding.fit(content.size(), limits.max()); - let size = limits.pad(padding).resolve(content.size()).pad(padding); - - content.move_to(Point::new(padding.left, padding.top)); - - layout::Node::with_children(size, vec![content]) + layout::padded(limits, width, height, padding, layout_content) } /// Returns the [`mouse::Interaction`] of a [`Button`]. diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 390f4d92..4e42a671 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -14,8 +14,9 @@ use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; -use crate::core::{Clipboard, Element, Shell, Widget}; -use crate::core::{Length, Rectangle, Size, Vector}; +use crate::core::{ + Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget, +}; use crate::graphics::geometry; use std::marker::PhantomData; @@ -119,12 +120,11 @@ where tree::State::new(P::State::default()) } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -133,10 +133,7 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) + layout::atomic(limits, self.width, self.height) } fn on_event( diff --git a/widget/src/canvas/event.rs b/widget/src/canvas/event.rs index 1288365f..a8eb47f7 100644 --- a/widget/src/canvas/event.rs +++ b/widget/src/canvas/event.rs @@ -8,7 +8,7 @@ pub use crate::core::event::Status; /// A [`Canvas`] event. /// /// [`Canvas`]: crate::Canvas -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum Event { /// A mouse event. Mouse(mouse::Event), diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index d7fdf339..0353b3ad 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -174,12 +174,11 @@ where tree::State::new(widget::text::State::<Renderer::Paragraph>::default()) } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: Length::Shrink, + } } fn layout( @@ -266,7 +265,7 @@ where style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { let is_mouse_over = cursor.is_over(layout.bounds()); @@ -315,6 +314,7 @@ where }, bounds.center(), custom_style.icon_color, + *viewport, ); } } @@ -330,6 +330,7 @@ where crate::text::Appearance { color: custom_style.text_color, }, + viewport, ); } } diff --git a/widget/src/column.rs b/widget/src/column.rs index 42e90ac1..d6eea84b 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -7,7 +7,7 @@ use crate::core::renderer; use crate::core::widget::{Operation, Tree}; use crate::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, - Shell, Widget, + Shell, Size, Widget, }; /// A container that distributes its contents vertically. @@ -22,16 +22,12 @@ pub struct Column<'a, Message, Renderer = crate::Renderer> { children: Vec<Element<'a, Message, Renderer>>, } -impl<'a, Message, Renderer> Column<'a, Message, Renderer> { +impl<'a, Message, Renderer> Column<'a, Message, Renderer> +where + Renderer: crate::core::Renderer, +{ /// Creates an empty [`Column`]. pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Column`] with the given elements. - pub fn with_children( - children: Vec<Element<'a, Message, Renderer>>, - ) -> Self { Column { spacing: 0.0, padding: Padding::ZERO, @@ -39,10 +35,17 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { height: Length::Shrink, max_width: f32::INFINITY, align_items: Alignment::Start, - children, + children: Vec::new(), } } + /// Creates a [`Column`] with the given elements. + pub fn with_children( + children: impl IntoIterator<Item = Element<'a, Message, Renderer>>, + ) -> Self { + children.into_iter().fold(Self::new(), Self::push) + } + /// Sets the vertical spacing _between_ elements. /// /// Custom margins per element do not exist in iced. You should use this @@ -88,12 +91,26 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { mut self, child: impl Into<Element<'a, Message, Renderer>>, ) -> Self { - self.children.push(child.into()); + let child = child.into(); + let size = child.as_widget().size_hint(); + + if size.width.is_fill() { + self.width = Length::Fill; + } + + if size.height.is_fill() { + self.height = Length::Fill; + } + + self.children.push(child); self } } -impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> { +impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> +where + Renderer: crate::core::Renderer, +{ fn default() -> Self { Self::new() } @@ -112,12 +129,11 @@ where tree.diff_children(&self.children); } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -126,15 +142,14 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits - .max_width(self.max_width) - .width(self.width) - .height(self.height); + let limits = limits.max_width(self.max_width); layout::flex::resolve( layout::flex::Axis::Vertical, renderer, &limits, + self.width, + self.height, self.padding, self.spacing, self.align_items, @@ -224,15 +239,17 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - for ((child, state), layout) in self - .children - .iter() - .zip(&tree.children) - .zip(layout.children()) - { - child - .as_widget() - .draw(state, renderer, theme, style, layout, cursor, viewport); + if let Some(viewport) = layout.bounds().intersection(viewport) { + for ((child, state), layout) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + { + child.as_widget().draw( + state, renderer, theme, style, layout, cursor, &viewport, + ); + } } } diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 768c2402..73beeac3 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -1,6 +1,7 @@ //! Display a dropdown list of searchable and selectable options. use crate::core::event::{self, Event}; use crate::core::keyboard; +use crate::core::keyboard::key; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::overlay; @@ -8,7 +9,9 @@ use crate::core::renderer; use crate::core::text; use crate::core::time::Instant; use crate::core::widget::{self, Widget}; -use crate::core::{Clipboard, Element, Length, Padding, Rectangle, Shell}; +use crate::core::{ + Clipboard, Element, Length, Padding, Rectangle, Shell, Size, +}; use crate::overlay::menu; use crate::text::LineHeight; use crate::{container, scrollable, text_input, TextInput}; @@ -297,12 +300,8 @@ where + scrollable::StyleSheet + menu::StyleSheet, { - fn width(&self) -> Length { - Widget::<TextInputEvent, Renderer>::width(&self.text_input) - } - - fn height(&self) -> Length { - Widget::<TextInputEvent, Renderer>::height(&self.text_input) + fn size(&self) -> Size<Length> { + Widget::<TextInputEvent, Renderer>::size(&self.text_input) } fn layout( @@ -438,14 +437,14 @@ where } if let Event::Keyboard(keyboard::Event::KeyPressed { - key_code, + key: keyboard::Key::Named(named_key), modifiers, .. }) = event { let shift_modifer = modifiers.shift(); - match (key_code, shift_modifer) { - (keyboard::KeyCode::Enter, _) => { + match (named_key, shift_modifer) { + (key::Named::Enter, _) => { if let Some(index) = &menu.hovered_option { if let Some(option) = state.filtered_options.options.get(*index) @@ -457,8 +456,7 @@ where event_status = event::Status::Captured; } - (keyboard::KeyCode::Up, _) - | (keyboard::KeyCode::Tab, true) => { + (key::Named::ArrowUp, _) | (key::Named::Tab, true) => { if let Some(index) = &mut menu.hovered_option { if *index == 0 { *index = state @@ -494,8 +492,8 @@ where event_status = event::Status::Captured; } - (keyboard::KeyCode::Down, _) - | (keyboard::KeyCode::Tab, false) + (key::Named::ArrowDown, _) + | (key::Named::Tab, false) if !modifiers.shift() => { if let Some(index) = &mut menu.hovered_option { @@ -622,7 +620,7 @@ where _style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { let is_focused = { let text_input_state = tree.children[0] @@ -645,6 +643,7 @@ where layout, cursor, selection, + viewport, ); } diff --git a/widget/src/container.rs b/widget/src/container.rs index ee7a4965..cffb0458 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -46,17 +46,20 @@ where where T: Into<Element<'a, Message, Renderer>>, { + let content = content.into(); + let size = content.as_widget().size_hint(); + Container { id: None, padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, + width: size.width.fluid(), + height: size.height.fluid(), max_width: f32::INFINITY, max_height: f32::INFINITY, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, style: Default::default(), - content: content.into(), + content, } } @@ -152,12 +155,11 @@ where self.content.as_widget().diff(tree); } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -252,21 +254,23 @@ where ) { let style = theme.appearance(&self.style); - draw_background(renderer, &style, layout.bounds()); - - self.content.as_widget().draw( - tree, - renderer, - theme, - &renderer::Style { - text_color: style - .text_color - .unwrap_or(renderer_style.text_color), - }, - layout.children().next().unwrap(), - cursor, - viewport, - ); + if let Some(viewport) = layout.bounds().intersection(viewport) { + draw_background(renderer, &style, layout.bounds()); + + self.content.as_widget().draw( + tree, + renderer, + theme, + &renderer::Style { + text_color: style + .text_color + .unwrap_or(renderer_style.text_color), + }, + layout.children().next().unwrap(), + cursor, + &viewport, + ); + } } fn overlay<'b>( @@ -309,25 +313,20 @@ pub fn layout( vertical_alignment: alignment::Vertical, layout_content: impl FnOnce(&layout::Limits) -> layout::Node, ) -> layout::Node { - let limits = limits - .loose() - .max_width(max_width) - .max_height(max_height) - .width(width) - .height(height); - - let mut content = layout_content(&limits.pad(padding).loose()); - let padding = padding.fit(content.size(), limits.max()); - let size = limits.pad(padding).resolve(content.size()); - - content.move_to(Point::new(padding.left, padding.top)); - content.align( - Alignment::from(horizontal_alignment), - Alignment::from(vertical_alignment), - size, - ); - - layout::Node::with_children(size.pad(padding), vec![content]) + layout::positioned( + &limits.max_width(max_width).max_height(max_height), + width, + height, + padding, + |limits| layout_content(&limits.loose()), + |content, size| { + content.align( + Alignment::from(horizontal_alignment), + Alignment::from(vertical_alignment), + size, + ) + }, + ) } /// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`. diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 3c9c2b29..498dd76c 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -16,6 +16,7 @@ use crate::runtime::Command; use crate::scrollable::{self, Scrollable}; use crate::slider::{self, Slider}; use crate::text::{self, Text}; +use crate::text_editor::{self, TextEditor}; use crate::text_input::{self, TextInput}; use crate::toggler::{self, Toggler}; use crate::tooltip::{self, Tooltip}; @@ -33,7 +34,7 @@ macro_rules! column { $crate::Column::new() ); ($($x:expr),+ $(,)?) => ( - $crate::Column::with_children(vec![$($crate::core::Element::from($x)),+]) + $crate::Column::with_children([$($crate::core::Element::from($x)),+]) ); } @@ -46,7 +47,7 @@ macro_rules! row { $crate::Row::new() ); ($($x:expr),+ $(,)?) => ( - $crate::Row::with_children(vec![$($crate::core::Element::from($x)),+]) + $crate::Row::with_children([$($crate::core::Element::from($x)),+]) ); } @@ -64,9 +65,12 @@ where } /// Creates a new [`Column`] with the given children. -pub fn column<Message, Renderer>( - children: Vec<Element<'_, Message, Renderer>>, -) -> Column<'_, Message, Renderer> { +pub fn column<'a, Message, Renderer>( + children: impl IntoIterator<Item = Element<'a, Message, Renderer>>, +) -> Column<'a, Message, Renderer> +where + Renderer: core::Renderer, +{ Column::with_children(children) } @@ -76,6 +80,7 @@ pub fn keyed_column<'a, Key, Message, Renderer>( ) -> keyed::Column<'a, Key, Message, Renderer> where Key: Copy + PartialEq, + Renderer: core::Renderer, { keyed::Column::with_children(children) } @@ -83,9 +88,12 @@ where /// Creates a new [`Row`] with the given children. /// /// [`Row`]: crate::Row -pub fn row<Message, Renderer>( - children: Vec<Element<'_, Message, Renderer>>, -) -> Row<'_, Message, Renderer> { +pub fn row<'a, Message, Renderer>( + children: impl IntoIterator<Item = Element<'a, Message, Renderer>>, +) -> Row<'a, Message, Renderer> +where + Renderer: core::Renderer, +{ Row::with_children(children) } @@ -206,6 +214,20 @@ where TextInput::new(placeholder, value) } +/// Creates a new [`TextEditor`]. +/// +/// [`TextEditor`]: crate::TextEditor +pub fn text_editor<Message, Renderer>( + content: &text_editor::Content<Renderer>, +) -> TextEditor<'_, core::text::highlighter::PlainText, Message, Renderer> +where + Message: Clone, + Renderer: core::text::Renderer, + Renderer::Theme: text_editor::StyleSheet, +{ + TextEditor::new(content) +} + /// Creates a new [`Slider`]. /// /// [`Slider`]: crate::Slider @@ -249,7 +271,7 @@ pub fn pick_list<'a, Message, Renderer, T>( on_selected: impl Fn(T) -> Message + 'a, ) -> PickList<'a, T, Message, Renderer> where - T: ToString + Eq + 'static, + T: ToString + PartialEq + 'static, [T]: ToOwned<Owned = Vec<T>>, Renderer: core::text::Renderer, Renderer::Theme: pick_list::StyleSheet @@ -370,6 +392,17 @@ where crate::Canvas::new(program) } +/// Creates a new [`Shader`]. +/// +/// [`Shader`]: crate::Shader +#[cfg(feature = "wgpu")] +pub fn shader<Message, P>(program: P) -> crate::Shader<Message, P> +where + P: crate::shader::Program<Message>, +{ + crate::Shader::new(program) +} + /// Focuses the previous focusable widget. pub fn focus_previous<Message>() -> Command<Message> where diff --git a/widget/src/image.rs b/widget/src/image.rs index a0e89920..e906ac13 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -13,7 +13,7 @@ use crate::core::{ use std::hash::Hash; -pub use image::Handle; +pub use image::{FilterMethod, Handle}; /// Creates a new [`Viewer`] with the given image `Handle`. pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> { @@ -37,6 +37,7 @@ pub struct Image<Handle> { width: Length, height: Length, content_fit: ContentFit, + filter_method: FilterMethod, } impl<Handle> Image<Handle> { @@ -47,6 +48,7 @@ impl<Handle> Image<Handle> { width: Length::Shrink, height: Length::Shrink, content_fit: ContentFit::Contain, + filter_method: FilterMethod::default(), } } @@ -65,11 +67,15 @@ impl<Handle> Image<Handle> { /// Sets the [`ContentFit`] of the [`Image`]. /// /// Defaults to [`ContentFit::Contain`] - pub fn content_fit(self, content_fit: ContentFit) -> Self { - Self { - content_fit, - ..self - } + pub fn content_fit(mut self, content_fit: ContentFit) -> Self { + self.content_fit = content_fit; + self + } + + /// Sets the [`FilterMethod`] of the [`Image`]. + pub fn filter_method(mut self, filter_method: FilterMethod) -> Self { + self.filter_method = filter_method; + self } } @@ -93,7 +99,7 @@ where }; // The size to be available to the widget prior to `Shrink`ing - let raw_size = limits.width(width).height(height).resolve(image_size); + let raw_size = limits.resolve(width, height, image_size); // The uncropped size of the image when fit to the bounds above let full_size = content_fit.fit(image_size, raw_size); @@ -119,6 +125,7 @@ pub fn draw<Renderer, Handle>( layout: Layout<'_>, handle: &Handle, content_fit: ContentFit, + filter_method: FilterMethod, ) where Renderer: image::Renderer<Handle = Handle>, Handle: Clone + Hash, @@ -141,7 +148,7 @@ pub fn draw<Renderer, Handle>( ..bounds }; - renderer.draw(handle.clone(), drawing_bounds + offset); + renderer.draw(handle.clone(), filter_method, drawing_bounds + offset); }; if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height @@ -157,12 +164,11 @@ where Renderer: image::Renderer<Handle = Handle>, Handle: Clone + Hash, { - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -191,7 +197,13 @@ where _cursor: mouse::Cursor, _viewport: &Rectangle, ) { - draw(renderer, layout, &self.handle, self.content_fit); + draw( + renderer, + layout, + &self.handle, + self.content_fit, + self.filter_method, + ); } } diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 44624fc8..98080577 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -22,19 +22,21 @@ pub struct Viewer<Handle> { max_scale: f32, scale_step: f32, handle: Handle, + filter_method: image::FilterMethod, } impl<Handle> Viewer<Handle> { /// Creates a new [`Viewer`] with the given [`State`]. pub fn new(handle: Handle) -> Self { Viewer { + handle, padding: 0.0, width: Length::Shrink, height: Length::Shrink, min_scale: 0.25, max_scale: 10.0, scale_step: 0.10, - handle, + filter_method: image::FilterMethod::default(), } } @@ -95,12 +97,11 @@ where tree::State::new(State::new()) } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -111,10 +112,11 @@ where ) -> layout::Node { let Size { width, height } = renderer.dimensions(&self.handle); - let mut size = limits - .width(self.width) - .height(self.height) - .resolve(Size::new(width as f32, height as f32)); + let mut size = limits.resolve( + self.width, + self.height, + Size::new(width as f32, height as f32), + ); let expansion_size = if height > width { self.width @@ -329,6 +331,7 @@ where image::Renderer::draw( renderer, self.handle.clone(), + self.filter_method, Rectangle { x: bounds.x, y: bounds.y, diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 0ef82407..7f05a81e 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -8,7 +8,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; use crate::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, - Shell, Widget, + Shell, Size, Widget, }; /// A container that distributes its contents vertically. @@ -30,26 +30,10 @@ where impl<'a, Key, Message, Renderer> Column<'a, Key, Message, Renderer> where Key: Copy + PartialEq, + Renderer: crate::core::Renderer, { /// Creates an empty [`Column`]. pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Column`] with the given elements. - pub fn with_children( - children: impl IntoIterator<Item = (Key, Element<'a, Message, Renderer>)>, - ) -> Self { - let (keys, children) = children.into_iter().fold( - (Vec::new(), Vec::new()), - |(mut keys, mut children), (key, child)| { - keys.push(key); - children.push(child); - - (keys, children) - }, - ); - Column { spacing: 0.0, padding: Padding::ZERO, @@ -57,11 +41,20 @@ where height: Length::Shrink, max_width: f32::INFINITY, align_items: Alignment::Start, - keys, - children, + keys: Vec::new(), + children: Vec::new(), } } + /// Creates a [`Column`] with the given elements. + pub fn with_children( + children: impl IntoIterator<Item = (Key, Element<'a, Message, Renderer>)>, + ) -> Self { + children + .into_iter() + .fold(Self::new(), |column, (key, child)| column.push(key, child)) + } + /// Sets the vertical spacing _between_ elements. /// /// Custom margins per element do not exist in iced. You should use this @@ -108,8 +101,19 @@ where key: Key, child: impl Into<Element<'a, Message, Renderer>>, ) -> Self { + let child = child.into(); + let size = child.as_widget().size_hint(); + + if size.width.is_fill() { + self.width = Length::Fill; + } + + if size.height.is_fill() { + self.height = Length::Fill; + } + self.keys.push(key); - self.children.push(child.into()); + self.children.push(child); self } } @@ -117,6 +121,7 @@ where impl<'a, Key, Message, Renderer> Default for Column<'a, Key, Message, Renderer> where Key: Copy + PartialEq, + Renderer: crate::core::Renderer, { fn default() -> Self { Self::new() @@ -173,12 +178,11 @@ where } } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -196,6 +200,8 @@ where layout::flex::Axis::Vertical, renderer, &limits, + self.width, + self.height, self.padding, self.spacing, self.align_items, diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 589dd938..e9edbb4c 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -18,7 +18,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Widget}; use crate::core::Element; use crate::core::{ - self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, + self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, Vector, }; use crate::runtime::overlay::Nested; @@ -142,12 +142,15 @@ where } } - fn width(&self) -> Length { - self.with_element(|element| element.as_widget().width()) + fn size(&self) -> Size<Length> { + self.with_element(|element| element.as_widget().size()) } - fn height(&self) -> Length { - self.with_element(|element| element.as_widget().height()) + fn size_hint(&self) -> Size<Length> { + Size { + width: Length::Shrink, + height: Length::Shrink, + } } fn layout( @@ -333,9 +336,10 @@ where renderer: &Renderer, bounds: Size, position: Point, + translation: Vector, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - overlay.layout(renderer, bounds, position) + overlay.layout(renderer, bounds, position, translation) }) .unwrap_or_default() } diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index d454b72b..3684e0c9 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -244,12 +244,15 @@ where self.rebuild_element_if_necessary(); } - fn width(&self) -> Length { - self.with_element(|element| element.as_widget().width()) + fn size(&self) -> Size<Length> { + self.with_element(|element| element.as_widget().size()) } - fn height(&self) -> Length { - self.with_element(|element| element.as_widget().height()) + fn size_hint(&self) -> Size<Length> { + Size { + width: Length::Shrink, + height: Length::Shrink, + } } fn layout( @@ -577,9 +580,10 @@ where renderer: &Renderer, bounds: Size, position: Point, + translation: Vector, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - overlay.layout(renderer, bounds, position) + overlay.layout(renderer, bounds, position, translation) }) .unwrap_or_default() } diff --git a/widget/src/lazy/helpers.rs b/widget/src/lazy/helpers.rs index 8ca9cb86..5dc60d52 100644 --- a/widget/src/lazy/helpers.rs +++ b/widget/src/lazy/helpers.rs @@ -6,6 +6,7 @@ use std::hash::Hash; /// Creates a new [`Lazy`] widget with the given data `Dependency` and a /// closure that can turn this data into a widget tree. +#[cfg(feature = "lazy")] pub fn lazy<'a, Message, Renderer, Dependency, View>( dependency: Dependency, view: impl Fn(&Dependency) -> View + 'a, @@ -19,6 +20,7 @@ where /// Turns an implementor of [`Component`] into an [`Element`] that can be /// embedded in any application. +#[cfg(feature = "lazy")] pub fn component<'a, C, Message, Renderer>( component: C, ) -> Element<'a, Message, Renderer> @@ -37,6 +39,7 @@ where /// The `view` closure will be provided with the current [`Size`] of /// the [`Responsive`] widget and, therefore, can be used to build the /// contents of the widget in a responsive way. +#[cfg(feature = "lazy")] pub fn responsive<'a, Message, Renderer>( f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a, ) -> Responsive<'a, Message, Renderer> diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index ed471988..1df0866f 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -6,7 +6,8 @@ use crate::core::renderer; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, + self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, + Widget, }; use crate::horizontal_space; use crate::runtime::overlay::Nested; @@ -134,12 +135,11 @@ where }) } - fn width(&self) -> Length { - Length::Fill - } - - fn height(&self) -> Length { - Length::Fill + fn size(&self) -> Size<Length> { + Size { + width: Length::Fill, + height: Length::Fill, + } } fn layout( @@ -367,9 +367,10 @@ where renderer: &Renderer, bounds: Size, position: Point, + translation: Vector, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - overlay.layout(renderer, bounds, position) + overlay.layout(renderer, bounds, position, translation) }) .unwrap_or_default() } diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 6feb948c..07378d83 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -35,6 +35,7 @@ pub mod scrollable; pub mod slider; pub mod space; pub mod text; +pub mod text_editor; pub mod text_input; pub mod toggler; pub mod tooltip; @@ -86,6 +87,8 @@ pub use space::Space; #[doc(no_inline)] pub use text::Text; #[doc(no_inline)] +pub use text_editor::TextEditor; +#[doc(no_inline)] pub use text_input::TextInput; #[doc(no_inline)] pub use toggler::Toggler; @@ -94,6 +97,13 @@ pub use tooltip::Tooltip; #[doc(no_inline)] pub use vertical_slider::VerticalSlider; +#[cfg(feature = "wgpu")] +pub mod shader; + +#[cfg(feature = "wgpu")] +#[doc(no_inline)] +pub use shader::Shader; + #[cfg(feature = "svg")] pub mod svg; diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 3a5b01a3..87cac3a7 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -8,7 +8,7 @@ use crate::core::renderer; use crate::core::touch; use crate::core::widget::{tree, Operation, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Rectangle, Shell, Widget, + Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Widget, }; /// Emit messages on mouse events. @@ -110,12 +110,8 @@ where tree.diff_children(std::slice::from_ref(&self.content)); } - fn width(&self) -> Length { - self.content.as_widget().width() - } - - fn height(&self) -> Length { - self.content.as_widget().height() + fn size(&self) -> Size<Length> { + self.content.as_widget().size() } fn layout( diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b293f9fa..f83eebea 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -236,6 +236,7 @@ where renderer: &Renderer, bounds: Size, position: Point, + _translation: Vector, ) -> layout::Node { let space_below = bounds.height - (position.y + self.target_height); let space_above = position.y; @@ -253,15 +254,14 @@ where ) .width(self.width); - let mut node = self.container.layout(self.state, renderer, &limits); + let node = self.container.layout(self.state, renderer, &limits); + let size = node.size(); node.move_to(if space_below > space_above { position + Vector::new(0.0, self.target_height) } else { - position - Vector::new(0.0, node.size().height) - }); - - node + position - Vector::new(0.0, size.height) + }) } fn on_event( @@ -342,12 +342,11 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { - fn width(&self) -> Length { - Length::Fill - } - - fn height(&self) -> Length { - Length::Shrink + fn size(&self) -> Size<Length> { + Size { + width: Length::Fill, + height: Length::Shrink, + } } fn layout( @@ -358,7 +357,6 @@ where ) -> layout::Node { use std::f32; - let limits = limits.width(Length::Fill).height(Length::Shrink); let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); @@ -371,7 +369,7 @@ where * self.options.len() as f32, ); - limits.resolve(intrinsic) + limits.resolve(Length::Fill, Length::Shrink, intrinsic) }; layout::Node::new(size) @@ -543,6 +541,7 @@ where } else { appearance.text_color }, + *viewport, ); } } diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 2d25a543..cf1f0455 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -265,12 +265,11 @@ where } } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -490,8 +489,7 @@ pub fn layout<Renderer, T>( &layout::Limits, ) -> layout::Node, ) -> layout::Node { - let limits = limits.width(width).height(height); - let size = limits.resolve(Size::ZERO); + let size = limits.resolve(width, height, Size::ZERO); let regions = node.pane_regions(spacing, size); let children = contents @@ -500,16 +498,14 @@ pub fn layout<Renderer, T>( let region = regions.get(&pane)?; let size = Size::new(region.width, region.height); - let mut node = layout_content( + let node = layout_content( content, tree, renderer, &layout::Limits::new(size, size), ); - node.move_to(Point::new(region.x, region.y)); - - Some(node) + Some(node.move_to(Point::new(region.x, region.y))) }) .collect(); @@ -531,6 +527,8 @@ pub fn update<'a, Message, T: Draggable>( on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, on_resize: &Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, ) -> event::Status { + const DRAG_DEADBAND_DISTANCE: f32 = 10.0; + let mut event_status = event::Status::Ignored; match event { @@ -572,7 +570,6 @@ pub fn update<'a, Message, T: Draggable>( shell, contents, on_click, - on_drag, ); } } @@ -584,7 +581,6 @@ pub fn update<'a, Message, T: Draggable>( shell, contents, on_click, - on_drag, ); } } @@ -637,7 +633,49 @@ pub fn update<'a, Message, T: Draggable>( } Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { - if let Some((_, on_resize)) = on_resize { + if let Some((_, origin)) = action.clicked_pane() { + if let Some(on_drag) = &on_drag { + let bounds = layout.bounds(); + + if let Some(cursor_position) = cursor.position_over(bounds) + { + let mut clicked_region = contents + .zip(layout.children()) + .filter(|(_, layout)| { + layout.bounds().contains(cursor_position) + }); + + if let Some(((pane, content), layout)) = + clicked_region.next() + { + if content + .can_be_dragged_at(layout, cursor_position) + { + let pane_position = layout.position(); + + let new_origin = cursor_position + - Vector::new( + pane_position.x, + pane_position.y, + ); + + if new_origin.distance(origin) + > DRAG_DEADBAND_DISTANCE + { + *action = state::Action::Dragging { + pane, + origin, + }; + + shell.publish(on_drag(DragEvent::Picked { + pane, + })); + } + } + } + } + } + } else if let Some((_, on_resize)) = on_resize { if let Some((split, _)) = action.picked_split() { let bounds = layout.bounds(); @@ -712,7 +750,6 @@ fn click_pane<'a, Message, T>( shell: &mut Shell<'_, Message>, contents: impl Iterator<Item = (Pane, T)>, on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, - on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, ) where T: Draggable, { @@ -720,23 +757,15 @@ fn click_pane<'a, Message, T>( .zip(layout.children()) .filter(|(_, layout)| layout.bounds().contains(cursor_position)); - if let Some(((pane, content), layout)) = clicked_region.next() { + if let Some(((pane, _), layout)) = clicked_region.next() { if let Some(on_click) = &on_click { shell.publish(on_click(pane)); } - if let Some(on_drag) = &on_drag { - if content.can_be_dragged_at(layout, cursor_position) { - let pane_position = layout.position(); - - let origin = cursor_position - - Vector::new(pane_position.x, pane_position.y); - - *action = state::Action::Dragging { pane, origin }; - - shell.publish(on_drag(DragEvent::Picked { pane })); - } - } + let pane_position = layout.position(); + let origin = + cursor_position - Vector::new(pane_position.x, pane_position.y); + *action = state::Action::Clicking { pane, origin }; } } @@ -749,7 +778,7 @@ pub fn mouse_interaction( spacing: f32, resize_leeway: Option<f32>, ) -> Option<mouse::Interaction> { - if action.picked_pane().is_some() { + if action.clicked_pane().is_some() || action.picked_pane().is_some() { return Some(mouse::Interaction::Grabbing); } diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 826ea663..ee00f186 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -165,7 +165,7 @@ where let title_bar_size = title_bar_layout.size(); - let mut body_layout = self.body.as_widget().layout( + let body_layout = self.body.as_widget().layout( &mut tree.children[0], renderer, &layout::Limits::new( @@ -177,11 +177,12 @@ where ), ); - body_layout.move_to(Point::new(0.0, title_bar_size.height)); - layout::Node::with_children( max_size, - vec![title_bar_layout, body_layout], + vec![ + title_bar_layout, + body_layout.move_to(Point::new(0.0, title_bar_size.height)), + ], ) } else { self.body.as_widget().layout( diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 481cd770..5d1fe254 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -403,6 +403,15 @@ pub enum Action { /// /// [`PaneGrid`]: super::PaneGrid Idle, + /// A [`Pane`] in the [`PaneGrid`] is being clicked. + /// + /// [`PaneGrid`]: super::PaneGrid + Clicking { + /// The [`Pane`] being clicked. + pane: Pane, + /// The starting [`Point`] of the click interaction. + origin: Point, + }, /// A [`Pane`] in the [`PaneGrid`] is being dragged. /// /// [`PaneGrid`]: super::PaneGrid @@ -432,6 +441,14 @@ impl Action { } } + /// Returns the current [`Pane`] that is being clicked, if any. + pub fn clicked_pane(&self) -> Option<(Pane, Point)> { + match *self { + Action::Clicking { pane, origin, .. } => Some((pane, origin)), + _ => None, + } + } + /// Returns the current [`Split`] that is being dragged, if any. pub fn picked_split(&self) -> Option<(Split, Axis)> { match *self { diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index f4dbb6b1..eb21b743 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -217,7 +217,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.pad(self.padding); + let limits = limits.shrink(self.padding); let max_size = limits.max(); let title_layout = self.content.as_widget().layout( @@ -228,8 +228,8 @@ where let title_size = title_layout.size(); - let mut node = if let Some(controls) = &self.controls { - let mut controls_layout = controls.as_widget().layout( + let node = if let Some(controls) = &self.controls { + let controls_layout = controls.as_widget().layout( &mut tree.children[1], renderer, &layout::Limits::new(Size::ZERO, max_size), @@ -240,11 +240,13 @@ where let height = title_size.height.max(controls_size.height); - controls_layout.move_to(Point::new(space_before_controls, 0.0)); - layout::Node::with_children( Size::new(max_size.width, height), - vec![title_layout, controls_layout], + vec![ + title_layout, + controls_layout + .move_to(Point::new(space_before_controls, 0.0)), + ], ) } else { layout::Node::with_children( @@ -253,9 +255,7 @@ where ) }; - node.move_to(Point::new(self.padding.left, self.padding.top)); - - layout::Node::with_children(node.size().pad(self.padding), vec![node]) + layout::Node::container(node, self.padding) } pub(crate) fn operate( diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 27f32907..2e3aab6f 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -45,7 +45,7 @@ where impl<'a, T: 'a, Message, Renderer> PickList<'a, T, Message, Renderer> where - T: ToString + Eq, + T: ToString + PartialEq, [T]: ToOwned<Owned = Vec<T>>, Renderer: text::Renderer, Renderer::Theme: StyleSheet @@ -145,7 +145,7 @@ where impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer> for PickList<'a, T, Message, Renderer> where - T: Clone + ToString + Eq + 'static, + T: Clone + ToString + PartialEq + 'static, [T]: ToOwned<Owned = Vec<T>>, Message: 'a, Renderer: text::Renderer + 'a, @@ -164,12 +164,11 @@ where tree::State::new(State::<Renderer::Paragraph>::new()) } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: Length::Shrink, + } } fn layout( @@ -235,7 +234,7 @@ where _style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { let font = self.font.unwrap_or_else(|| renderer.default_font()); draw( @@ -253,6 +252,7 @@ where &self.handle, &self.style, || tree.state.downcast_ref::<State<Renderer::Paragraph>>(), + viewport, ); } @@ -281,7 +281,7 @@ where impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>> for Element<'a, Message, Renderer> where - T: Clone + ToString + Eq + 'static, + T: Clone + ToString + PartialEq + 'static, [T]: ToOwned<Owned = Vec<T>>, Message: 'a, Renderer: text::Renderer + 'a, @@ -392,7 +392,6 @@ where { use std::f32; - let limits = limits.width(width).height(Length::Shrink).pad(padding); let font = font.unwrap_or_else(|| renderer.default_font()); let text_size = text_size.unwrap_or_else(|| renderer.default_size()); @@ -415,23 +414,17 @@ where for (option, paragraph) in options.iter().zip(state.options.iter_mut()) { let label = option.to_string(); - renderer.update_paragraph( - paragraph, - Text { - content: &label, - ..option_text - }, - ); + paragraph.update(Text { + content: &label, + ..option_text + }); } if let Some(placeholder) = placeholder { - renderer.update_paragraph( - &mut state.placeholder, - Text { - content: placeholder, - ..option_text - }, - ); + state.placeholder.update(Text { + content: placeholder, + ..option_text + }); } let max_width = match width { @@ -456,7 +449,11 @@ where f32::from(text_line_height.to_absolute(text_size)), ); - limits.resolve(intrinsic).pad(padding) + limits + .width(width) + .shrink(padding) + .resolve(width, Length::Shrink, intrinsic) + .expand(padding) }; layout::Node::new(size) @@ -637,6 +634,7 @@ pub fn draw<'a, T, Renderer>( handle: &Handle<Renderer::Font>, style: &<Renderer::Theme as StyleSheet>::Style, state: impl FnOnce() -> &'a State<Renderer::Paragraph>, + viewport: &Rectangle, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -721,6 +719,7 @@ pub fn draw<'a, T, Renderer>( bounds.center_y(), ), style.handle_color, + *viewport, ); } @@ -749,6 +748,7 @@ pub fn draw<'a, T, Renderer>( } else { style.placeholder_color }, + *viewport, ); } } diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 07de72d5..15f1277b 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -85,12 +85,11 @@ where Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)) + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)), + } } fn layout( @@ -99,13 +98,11 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits - .width(self.width) - .height(self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT))); - - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) + layout::atomic( + limits, + self.width, + self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)), + ) } fn draw( diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 1dc4da7f..a229eb59 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -50,12 +50,11 @@ impl<'a> QRCode<'a> { } impl<'a, Message, Theme> Widget<Message, Renderer<Theme>> for QRCode<'a> { - fn width(&self) -> Length { - Length::Shrink - } - - fn height(&self) -> Length { - Length::Shrink + fn size(&self) -> Size<Length> { + Size { + width: Length::Shrink, + height: Length::Shrink, + } } fn layout( diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 57acc033..f91b20b1 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -201,12 +201,11 @@ where tree::State::new(widget::text::State::<Renderer::Paragraph>::default()) } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: Length::Shrink, + } } fn layout( @@ -291,7 +290,7 @@ where style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { let is_mouse_over = cursor.is_over(layout.bounds()); @@ -349,6 +348,7 @@ where crate::text::Appearance { color: custom_style.text_color, }, + viewport, ); } } diff --git a/widget/src/row.rs b/widget/src/row.rs index 7ca90fbb..90fd2926 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -7,7 +7,7 @@ use crate::core::renderer; use crate::core::widget::{Operation, Tree}; use crate::core::{ Alignment, Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, - Widget, + Size, Widget, }; /// A container that distributes its contents horizontally. @@ -21,26 +21,29 @@ pub struct Row<'a, Message, Renderer = crate::Renderer> { children: Vec<Element<'a, Message, Renderer>>, } -impl<'a, Message, Renderer> Row<'a, Message, Renderer> { +impl<'a, Message, Renderer> Row<'a, Message, Renderer> +where + Renderer: crate::core::Renderer, +{ /// Creates an empty [`Row`]. pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Row`] with the given elements. - pub fn with_children( - children: Vec<Element<'a, Message, Renderer>>, - ) -> Self { Row { spacing: 0.0, padding: Padding::ZERO, width: Length::Shrink, height: Length::Shrink, align_items: Alignment::Start, - children, + children: Vec::new(), } } + /// Creates a [`Row`] with the given elements. + pub fn with_children( + children: impl IntoIterator<Item = Element<'a, Message, Renderer>>, + ) -> Self { + children.into_iter().fold(Self::new(), Self::push) + } + /// Sets the horizontal spacing _between_ elements. /// /// Custom margins per element do not exist in iced. You should use this @@ -80,12 +83,26 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { mut self, child: impl Into<Element<'a, Message, Renderer>>, ) -> Self { - self.children.push(child.into()); + let child = child.into(); + let size = child.as_widget().size_hint(); + + if size.width.is_fill() { + self.width = Length::Fill; + } + + if size.height.is_fill() { + self.height = Length::Fill; + } + + self.children.push(child); self } } -impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> { +impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> +where + Renderer: crate::core::Renderer, +{ fn default() -> Self { Self::new() } @@ -104,12 +121,11 @@ where tree.diff_children(&self.children); } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -118,12 +134,12 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - layout::flex::resolve( layout::flex::Axis::Horizontal, renderer, - &limits, + limits, + self.width, + self.height, self.padding, self.spacing, self.align_items, @@ -213,15 +229,17 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - for ((child, state), layout) in self - .children - .iter() - .zip(&tree.children) - .zip(layout.children()) - { - child - .as_widget() - .draw(state, renderer, theme, style, layout, cursor, viewport); + if let Some(viewport) = layout.bounds().intersection(viewport) { + for ((child, state), layout) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + { + child.as_widget().draw( + state, renderer, theme, style, layout, cursor, &viewport, + ); + } } } diff --git a/widget/src/rule.rs b/widget/src/rule.rs index b5c5fa55..cded9cb1 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -62,12 +62,11 @@ where Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -76,9 +75,7 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - - layout::Node::new(limits.resolve(Size::ZERO)) + layout::atomic(limits, self.width, self.height) } fn draw( diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 49aed2f0..70db490a 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -220,12 +220,11 @@ where tree.diff_children(std::slice::from_ref(&self.content)); } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -470,28 +469,25 @@ pub fn layout<Renderer>( direction: &Direction, layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, ) -> layout::Node { - let limits = limits.width(width).height(height); - - let child_limits = layout::Limits::new( - Size::new(limits.min().width, limits.min().height), - Size::new( - if direction.horizontal().is_some() { - f32::INFINITY - } else { - limits.max().width - }, - if direction.vertical().is_some() { - f32::MAX - } else { - limits.max().height - }, - ), - ); - - let content = layout_content(renderer, &child_limits); - let size = limits.resolve(content.size()); + layout::contained(limits, width, height, |limits| { + let child_limits = layout::Limits::new( + Size::new(limits.min().width, limits.min().height), + Size::new( + if direction.horizontal().is_some() { + f32::INFINITY + } else { + limits.max().width + }, + if direction.vertical().is_some() { + f32::MAX + } else { + limits.max().height + }, + ), + ); - layout::Node::with_children(size, vec![content]) + layout_content(renderer, &child_limits) + }) } /// Processes an [`Event`] and updates the [`State`] of a [`Scrollable`] diff --git a/widget/src/shader.rs b/widget/src/shader.rs new file mode 100644 index 00000000..16b68c55 --- /dev/null +++ b/widget/src/shader.rs @@ -0,0 +1,216 @@ +//! A custom shader widget for wgpu applications. +mod event; +mod program; + +pub use event::Event; +pub use program::Program; + +use crate::core; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::{self, Widget}; +use crate::core::window; +use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size}; +use crate::renderer::wgpu::primitive::pipeline; + +use std::marker::PhantomData; + +pub use crate::renderer::wgpu::wgpu; +pub use pipeline::{Primitive, Storage}; + +/// A widget which can render custom shaders with Iced's `wgpu` backend. +/// +/// Must be initialized with a [`Program`], which describes the internal widget state & how +/// its [`Program::Primitive`]s are drawn. +#[allow(missing_debug_implementations)] +pub struct Shader<Message, P: Program<Message>> { + width: Length, + height: Length, + program: P, + _message: PhantomData<Message>, +} + +impl<Message, P: Program<Message>> Shader<Message, P> { + /// Create a new custom [`Shader`]. + pub fn new(program: P) -> Self { + Self { + width: Length::Fixed(100.0), + height: Length::Fixed(100.0), + program, + _message: PhantomData, + } + } + + /// Set the `width` of the custom [`Shader`]. + pub fn width(mut self, width: impl Into<Length>) -> Self { + self.width = width.into(); + self + } + + /// Set the `height` of the custom [`Shader`]. + pub fn height(mut self, height: impl Into<Length>) -> Self { + self.height = height.into(); + self + } +} + +impl<P, Message, Renderer> Widget<Message, Renderer> for Shader<Message, P> +where + P: Program<Message>, + Renderer: pipeline::Renderer, +{ + fn tag(&self) -> tree::Tag { + struct Tag<T>(T); + tree::Tag::of::<Tag<P::State>>() + } + + fn state(&self) -> tree::State { + tree::State::new(P::State::default()) + } + + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &self, + _tree: &mut Tree, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + layout::atomic(limits, self.width, self.height) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: crate::core::Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) -> event::Status { + let bounds = layout.bounds(); + + let custom_shader_event = match event { + core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), + core::Event::Keyboard(keyboard_event) => { + Some(Event::Keyboard(keyboard_event)) + } + core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), + core::Event::Window(_, window::Event::RedrawRequested(instant)) => { + Some(Event::RedrawRequested(instant)) + } + _ => None, + }; + + if let Some(custom_shader_event) = custom_shader_event { + let state = tree.state.downcast_mut::<P::State>(); + + let (event_status, message) = self.program.update( + state, + custom_shader_event, + bounds, + cursor, + shell, + ); + + if let Some(message) = message { + shell.publish(message); + } + + return event_status; + } + + event::Status::Ignored + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::<P::State>(); + + self.program.mouse_interaction(state, bounds, cursor) + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut Renderer, + _theme: &Renderer::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + _viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::<P::State>(); + + renderer.draw_pipeline_primitive( + bounds, + self.program.draw(state, cursor_position, bounds), + ); + } +} + +impl<'a, Message, Renderer, P> From<Shader<Message, P>> + for Element<'a, Message, Renderer> +where + Message: 'a, + Renderer: pipeline::Renderer, + P: Program<Message> + 'a, +{ + fn from(custom: Shader<Message, P>) -> Element<'a, Message, Renderer> { + Element::new(custom) + } +} + +impl<Message, T> Program<Message> for &T +where + T: Program<Message>, +{ + type State = T::State; + type Primitive = T::Primitive; + + fn update( + &self, + state: &mut Self::State, + event: Event, + bounds: Rectangle, + cursor: mouse::Cursor, + shell: &mut Shell<'_, Message>, + ) -> (event::Status, Option<Message>) { + T::update(self, state, event, bounds, cursor, shell) + } + + fn draw( + &self, + state: &Self::State, + cursor: mouse::Cursor, + bounds: Rectangle, + ) -> Self::Primitive { + T::draw(self, state, cursor, bounds) + } + + fn mouse_interaction( + &self, + state: &Self::State, + bounds: Rectangle, + cursor: mouse::Cursor, + ) -> mouse::Interaction { + T::mouse_interaction(self, state, bounds, cursor) + } +} diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs new file mode 100644 index 00000000..005c8725 --- /dev/null +++ b/widget/src/shader/event.rs @@ -0,0 +1,25 @@ +//! Handle events of a custom shader widget. +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::time::Instant; +use crate::core::touch; + +pub use crate::core::event::Status; + +/// A [`Shader`] event. +/// +/// [`Shader`]: crate::Shader +#[derive(Debug, Clone, PartialEq)] +pub enum Event { + /// A mouse event. + Mouse(mouse::Event), + + /// A touch event. + Touch(touch::Event), + + /// A keyboard event. + Keyboard(keyboard::Event), + + /// A window requested a redraw. + RedrawRequested(Instant), +} diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs new file mode 100644 index 00000000..6dd50404 --- /dev/null +++ b/widget/src/shader/program.rs @@ -0,0 +1,62 @@ +use crate::core::event; +use crate::core::mouse; +use crate::core::{Rectangle, Shell}; +use crate::renderer::wgpu::primitive::pipeline; +use crate::shader; + +/// The state and logic of a [`Shader`] widget. +/// +/// A [`Program`] can mutate the internal state of a [`Shader`] widget +/// and produce messages for an application. +/// +/// [`Shader`]: crate::Shader +pub trait Program<Message> { + /// The internal state of the [`Program`]. + type State: Default + 'static; + + /// The type of primitive this [`Program`] can draw. + type Primitive: pipeline::Primitive + 'static; + + /// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes + /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a + /// redraw for the window, etc. + /// + /// By default, this method does and returns nothing. + /// + /// [`State`]: Self::State + fn update( + &self, + _state: &mut Self::State, + _event: shader::Event, + _bounds: Rectangle, + _cursor: mouse::Cursor, + _shell: &mut Shell<'_, Message>, + ) -> (event::Status, Option<Message>) { + (event::Status::Ignored, None) + } + + /// Draws the [`Primitive`]. + /// + /// [`Primitive`]: Self::Primitive + fn draw( + &self, + state: &Self::State, + cursor: mouse::Cursor, + bounds: Rectangle, + ) -> Self::Primitive; + + /// Returns the current mouse interaction of the [`Program`]. + /// + /// The interaction returned will be in effect even if the cursor position is out of + /// bounds of the [`Shader`]'s program. + /// + /// [`Shader`]: crate::Shader + fn mouse_interaction( + &self, + _state: &Self::State, + _bounds: Rectangle, + _cursor: mouse::Cursor, + ) -> mouse::Interaction { + mouse::Interaction::default() + } +} diff --git a/widget/src/slider.rs b/widget/src/slider.rs index ac0982c8..1bc94661 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -159,12 +159,11 @@ where tree::State::new(State::new()) } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: Length::Shrink, + } } fn layout( @@ -173,10 +172,7 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) + layout::atomic(limits, self.width, self.height) } fn on_event( diff --git a/widget/src/space.rs b/widget/src/space.rs index e5a8f169..eef990d1 100644 --- a/widget/src/space.rs +++ b/widget/src/space.rs @@ -45,12 +45,11 @@ impl<Message, Renderer> Widget<Message, Renderer> for Space where Renderer: core::Renderer, { - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -59,9 +58,7 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - - layout::Node::new(limits.resolve(Size::ZERO)) + layout::atomic(limits, self.width, self.height) } fn draw( diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 2d01d1ab..2357cf65 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -96,12 +96,11 @@ where Renderer: svg::Renderer, Renderer::Theme: iced_style::svg::StyleSheet, { - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } } fn layout( @@ -115,10 +114,7 @@ where let image_size = Size::new(width as f32, height as f32); // The size to be available to the widget prior to `Shrink`ing - let raw_size = limits - .width(self.width) - .height(self.height) - .resolve(image_size); + let raw_size = limits.resolve(self.width, self.height, image_size); // The uncropped size of the image when fit to the bounds above let full_size = self.content_fit.fit(image_size, raw_size); @@ -145,7 +141,7 @@ where theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, - _cursor: mouse::Cursor, + cursor: mouse::Cursor, _viewport: &Rectangle, ) { let Size { width, height } = renderer.dimensions(&self.handle); @@ -153,6 +149,7 @@ where let bounds = layout.bounds(); let adjusted_fit = self.content_fit.fit(image_size, bounds.size()); + let is_mouse_over = cursor.is_over(bounds); let render = |renderer: &mut Renderer| { let offset = Vector::new( @@ -166,7 +163,11 @@ where ..bounds }; - let appearance = theme.appearance(&self.style); + let appearance = if is_mouse_over { + theme.hovered(&self.style) + } else { + theme.appearance(&self.style) + }; renderer.draw( self.handle.clone(), diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs new file mode 100644 index 00000000..09a0cac0 --- /dev/null +++ b/widget/src/text_editor.rs @@ -0,0 +1,736 @@ +//! Display a multi-line text input for text editing. +use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::keyboard::key; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::text::editor::{Cursor, Editor as _}; +use crate::core::text::highlighter::{self, Highlighter}; +use crate::core::text::{self, LineHeight}; +use crate::core::widget::{self, Widget}; +use crate::core::{ + Clipboard, Color, Element, Length, Padding, Pixels, Rectangle, Shell, Size, + Vector, +}; + +use std::cell::RefCell; +use std::fmt; +use std::ops::DerefMut; +use std::sync::Arc; + +pub use crate::style::text_editor::{Appearance, StyleSheet}; +pub use text::editor::{Action, Edit, Motion}; + +/// A multi-line text input. +#[allow(missing_debug_implementations)] +pub struct TextEditor<'a, Highlighter, Message, Renderer = crate::Renderer> +where + Highlighter: text::Highlighter, + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + content: &'a Content<Renderer>, + font: Option<Renderer::Font>, + text_size: Option<Pixels>, + line_height: LineHeight, + width: Length, + height: Length, + padding: Padding, + style: <Renderer::Theme as StyleSheet>::Style, + on_edit: Option<Box<dyn Fn(Action) -> Message + 'a>>, + highlighter_settings: Highlighter::Settings, + highlighter_format: fn( + &Highlighter::Highlight, + &Renderer::Theme, + ) -> highlighter::Format<Renderer::Font>, +} + +impl<'a, Message, Renderer> + TextEditor<'a, highlighter::PlainText, Message, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + /// Creates new [`TextEditor`] with the given [`Content`]. + pub fn new(content: &'a Content<Renderer>) -> Self { + Self { + content, + font: None, + text_size: None, + line_height: LineHeight::default(), + width: Length::Fill, + height: Length::Fill, + padding: Padding::new(5.0), + style: Default::default(), + on_edit: None, + highlighter_settings: (), + highlighter_format: |_highlight, _theme| { + highlighter::Format::default() + }, + } + } +} + +impl<'a, Highlighter, Message, Renderer> + TextEditor<'a, Highlighter, Message, Renderer> +where + Highlighter: text::Highlighter, + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + /// Sets the message that should be produced when some action is performed in + /// the [`TextEditor`]. + /// + /// If this method is not called, the [`TextEditor`] will be disabled. + pub fn on_action( + mut self, + on_edit: impl Fn(Action) -> Message + 'a, + ) -> Self { + self.on_edit = Some(Box::new(on_edit)); + self + } + + /// Sets the [`Font`] of the [`TextEditor`]. + /// + /// [`Font`]: text::Renderer::Font + pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { + self.font = Some(font.into()); + self + } + + /// Sets the [`Padding`] of the [`TextEditor`]. + pub fn padding(mut self, padding: impl Into<Padding>) -> Self { + self.padding = padding.into(); + self + } + + /// Highlights the [`TextEditor`] with the given [`Highlighter`] and + /// a strategy to turn its highlights into some text format. + pub fn highlight<H: text::Highlighter>( + self, + settings: H::Settings, + to_format: fn( + &H::Highlight, + &Renderer::Theme, + ) -> highlighter::Format<Renderer::Font>, + ) -> TextEditor<'a, H, Message, Renderer> { + TextEditor { + content: self.content, + font: self.font, + text_size: self.text_size, + line_height: self.line_height, + width: self.width, + height: self.height, + padding: self.padding, + style: self.style, + on_edit: self.on_edit, + highlighter_settings: settings, + highlighter_format: to_format, + } + } + + /// Sets the style of the [`TextEditor`]. + pub fn style( + mut self, + style: impl Into<<Renderer::Theme as StyleSheet>::Style>, + ) -> Self { + self.style = style.into(); + self + } +} + +/// The content of a [`TextEditor`]. +pub struct Content<R = crate::Renderer>(RefCell<Internal<R>>) +where + R: text::Renderer; + +struct Internal<R> +where + R: text::Renderer, +{ + editor: R::Editor, + is_dirty: bool, +} + +impl<R> Content<R> +where + R: text::Renderer, +{ + /// Creates an empty [`Content`]. + pub fn new() -> Self { + Self::with_text("") + } + + /// Creates a [`Content`] with the given text. + pub fn with_text(text: &str) -> Self { + Self(RefCell::new(Internal { + editor: R::Editor::with_text(text), + is_dirty: true, + })) + } + + /// Performs an [`Action`] on the [`Content`]. + pub fn perform(&mut self, action: Action) { + let internal = self.0.get_mut(); + + internal.editor.perform(action); + internal.is_dirty = true; + } + + /// Returns the amount of lines of the [`Content`]. + pub fn line_count(&self) -> usize { + self.0.borrow().editor.line_count() + } + + /// Returns the text of the line at the given index, if it exists. + pub fn line( + &self, + index: usize, + ) -> Option<impl std::ops::Deref<Target = str> + '_> { + std::cell::Ref::filter_map(self.0.borrow(), |internal| { + internal.editor.line(index) + }) + .ok() + } + + /// Returns an iterator of the text of the lines in the [`Content`]. + pub fn lines( + &self, + ) -> impl Iterator<Item = impl std::ops::Deref<Target = str> + '_> { + struct Lines<'a, Renderer: text::Renderer> { + internal: std::cell::Ref<'a, Internal<Renderer>>, + current: usize, + } + + impl<'a, Renderer: text::Renderer> Iterator for Lines<'a, Renderer> { + type Item = std::cell::Ref<'a, str>; + + fn next(&mut self) -> Option<Self::Item> { + let line = std::cell::Ref::filter_map( + std::cell::Ref::clone(&self.internal), + |internal| internal.editor.line(self.current), + ) + .ok()?; + + self.current += 1; + + Some(line) + } + } + + Lines { + internal: self.0.borrow(), + current: 0, + } + } + + /// Returns the text of the [`Content`]. + /// + /// Lines are joined with `'\n'`. + pub fn text(&self) -> String { + let mut text = self.lines().enumerate().fold( + String::new(), + |mut contents, (i, line)| { + if i > 0 { + contents.push('\n'); + } + + contents.push_str(&line); + + contents + }, + ); + + if !text.ends_with('\n') { + text.push('\n'); + } + + text + } + + /// Returns the selected text of the [`Content`]. + pub fn selection(&self) -> Option<String> { + self.0.borrow().editor.selection() + } + + /// Returns the current cursor position of the [`Content`]. + pub fn cursor_position(&self) -> (usize, usize) { + self.0.borrow().editor.cursor_position() + } +} + +impl<Renderer> Default for Content<Renderer> +where + Renderer: text::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +impl<Renderer> fmt::Debug for Content<Renderer> +where + Renderer: text::Renderer, + Renderer::Editor: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let internal = self.0.borrow(); + + f.debug_struct("Content") + .field("editor", &internal.editor) + .field("is_dirty", &internal.is_dirty) + .finish() + } +} + +struct State<Highlighter: text::Highlighter> { + is_focused: bool, + last_click: Option<mouse::Click>, + drag_click: Option<mouse::click::Kind>, + highlighter: RefCell<Highlighter>, + highlighter_settings: Highlighter::Settings, + highlighter_format_address: usize, +} + +impl<'a, Highlighter, Message, Renderer> Widget<Message, Renderer> + for TextEditor<'a, Highlighter, Message, Renderer> +where + Highlighter: text::Highlighter, + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> widget::tree::Tag { + widget::tree::Tag::of::<State<Highlighter>>() + } + + fn state(&self) -> widget::tree::State { + widget::tree::State::new(State { + is_focused: false, + last_click: None, + drag_click: None, + highlighter: RefCell::new(Highlighter::new( + &self.highlighter_settings, + )), + highlighter_settings: self.highlighter_settings.clone(), + highlighter_format_address: self.highlighter_format as usize, + }) + } + + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &self, + tree: &mut widget::Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> iced_renderer::core::layout::Node { + let mut internal = self.content.0.borrow_mut(); + let state = tree.state.downcast_mut::<State<Highlighter>>(); + + if state.highlighter_format_address != self.highlighter_format as usize + { + state.highlighter.borrow_mut().change_line(0); + + state.highlighter_format_address = self.highlighter_format as usize; + } + + if state.highlighter_settings != self.highlighter_settings { + state + .highlighter + .borrow_mut() + .update(&self.highlighter_settings); + + state.highlighter_settings = self.highlighter_settings.clone(); + } + + internal.editor.update( + limits.shrink(self.padding).max(), + self.font.unwrap_or_else(|| renderer.default_font()), + self.text_size.unwrap_or_else(|| renderer.default_size()), + self.line_height, + state.highlighter.borrow_mut().deref_mut(), + ); + + layout::Node::new(limits.max()) + } + + fn on_event( + &mut self, + tree: &mut widget::Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + _renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) -> event::Status { + let Some(on_edit) = self.on_edit.as_ref() else { + return event::Status::Ignored; + }; + + let state = tree.state.downcast_mut::<State<Highlighter>>(); + + let Some(update) = Update::from_event( + event, + state, + layout.bounds(), + self.padding, + cursor, + ) else { + return event::Status::Ignored; + }; + + match update { + Update::Click(click) => { + let action = match click.kind() { + mouse::click::Kind::Single => { + Action::Click(click.position()) + } + mouse::click::Kind::Double => Action::SelectWord, + mouse::click::Kind::Triple => Action::SelectLine, + }; + + state.is_focused = true; + state.last_click = Some(click); + state.drag_click = Some(click.kind()); + + shell.publish(on_edit(action)); + } + Update::Unfocus => { + state.is_focused = false; + state.drag_click = None; + } + Update::Release => { + state.drag_click = None; + } + Update::Action(action) => { + shell.publish(on_edit(action)); + } + Update::Copy => { + if let Some(selection) = self.content.selection() { + clipboard.write(selection); + } + } + Update::Paste => { + if let Some(contents) = clipboard.read() { + shell.publish(on_edit(Action::Edit(Edit::Paste( + Arc::new(contents), + )))); + } + } + } + + event::Status::Captured + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut Renderer, + theme: &<Renderer as renderer::Renderer>::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + + let mut internal = self.content.0.borrow_mut(); + let state = tree.state.downcast_ref::<State<Highlighter>>(); + + internal.editor.highlight( + self.font.unwrap_or_else(|| renderer.default_font()), + state.highlighter.borrow_mut().deref_mut(), + |highlight| (self.highlighter_format)(highlight, theme), + ); + + let is_disabled = self.on_edit.is_none(); + let is_mouse_over = cursor.is_over(bounds); + + let appearance = if is_disabled { + theme.disabled(&self.style) + } else if state.is_focused { + theme.focused(&self.style) + } else if is_mouse_over { + theme.hovered(&self.style) + } else { + theme.active(&self.style) + }; + + renderer.fill_quad( + renderer::Quad { + bounds, + border_radius: appearance.border_radius, + border_width: appearance.border_width, + border_color: appearance.border_color, + }, + appearance.background, + ); + + renderer.fill_editor( + &internal.editor, + bounds.position() + + Vector::new(self.padding.left, self.padding.top), + style.text_color, + *viewport, + ); + + let translation = Vector::new( + bounds.x + self.padding.left, + bounds.y + self.padding.top, + ); + + if state.is_focused { + match internal.editor.cursor() { + Cursor::Caret(position) => { + let position = position + translation; + + if bounds.contains(position) { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: position.x, + y: position.y, + width: 1.0, + height: self + .line_height + .to_absolute( + self.text_size.unwrap_or_else( + || renderer.default_size(), + ), + ) + .into(), + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + theme.value_color(&self.style), + ); + } + } + Cursor::Selection(ranges) => { + for range in ranges.into_iter().filter_map(|range| { + bounds.intersection(&(range + translation)) + }) { + renderer.fill_quad( + renderer::Quad { + bounds: range, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + theme.selection_color(&self.style), + ); + } + } + } + } + } + + fn mouse_interaction( + &self, + _state: &widget::Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let is_disabled = self.on_edit.is_none(); + + if cursor.is_over(layout.bounds()) { + if is_disabled { + mouse::Interaction::NotAllowed + } else { + mouse::Interaction::Text + } + } else { + mouse::Interaction::default() + } + } +} + +impl<'a, Highlighter, Message, Renderer> + From<TextEditor<'a, Highlighter, Message, Renderer>> + for Element<'a, Message, Renderer> +where + Highlighter: text::Highlighter, + Message: 'a, + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + fn from( + text_editor: TextEditor<'a, Highlighter, Message, Renderer>, + ) -> Self { + Self::new(text_editor) + } +} + +enum Update { + Click(mouse::Click), + Unfocus, + Release, + Action(Action), + Copy, + Paste, +} + +impl Update { + fn from_event<H: Highlighter>( + event: Event, + state: &State<H>, + bounds: Rectangle, + padding: Padding, + cursor: mouse::Cursor, + ) -> Option<Self> { + let action = |action| Some(Update::Action(action)); + let edit = |edit| action(Action::Edit(edit)); + + match event { + Event::Mouse(event) => match event { + mouse::Event::ButtonPressed(mouse::Button::Left) => { + if let Some(cursor_position) = cursor.position_in(bounds) { + let cursor_position = cursor_position + - Vector::new(padding.top, padding.left); + + let click = mouse::Click::new( + cursor_position, + state.last_click, + ); + + Some(Update::Click(click)) + } else if state.is_focused { + Some(Update::Unfocus) + } else { + None + } + } + mouse::Event::ButtonReleased(mouse::Button::Left) => { + Some(Update::Release) + } + mouse::Event::CursorMoved { .. } => match state.drag_click { + Some(mouse::click::Kind::Single) => { + let cursor_position = cursor.position_in(bounds)? + - Vector::new(padding.top, padding.left); + + action(Action::Drag(cursor_position)) + } + _ => None, + }, + mouse::Event::WheelScrolled { delta } + if cursor.is_over(bounds) => + { + action(Action::Scroll { + lines: match delta { + mouse::ScrollDelta::Lines { y, .. } => { + if y.abs() > 0.0 { + (y.signum() * -(y.abs() * 4.0).max(1.0)) + as i32 + } else { + 0 + } + } + mouse::ScrollDelta::Pixels { y, .. } => { + (-y / 4.0) as i32 + } + }, + }) + } + _ => None, + }, + Event::Keyboard(event) => match event { + keyboard::Event::KeyPressed { + key, + modifiers, + text, + .. + } if state.is_focused => { + if let keyboard::Key::Named(named_key) = key.as_ref() { + if let Some(motion) = motion(named_key) { + let motion = if platform::is_jump_modifier_pressed( + modifiers, + ) { + motion.widen() + } else { + motion + }; + + return action(if modifiers.shift() { + Action::Select(motion) + } else { + Action::Move(motion) + }); + } + } + + match key.as_ref() { + keyboard::Key::Named(key::Named::Enter) => { + edit(Edit::Enter) + } + keyboard::Key::Named(key::Named::Backspace) => { + edit(Edit::Backspace) + } + keyboard::Key::Named(key::Named::Delete) => { + edit(Edit::Delete) + } + keyboard::Key::Named(key::Named::Escape) => { + Some(Self::Unfocus) + } + keyboard::Key::Character("c") + if modifiers.command() => + { + Some(Self::Copy) + } + keyboard::Key::Character("v") + if modifiers.command() && !modifiers.alt() => + { + Some(Self::Paste) + } + _ => { + let text = text?; + + edit(Edit::Insert( + text.chars().next().unwrap_or_default(), + )) + } + } + } + _ => None, + }, + _ => None, + } + } +} + +fn motion(key: key::Named) -> Option<Motion> { + match key { + key::Named::ArrowLeft => Some(Motion::Left), + key::Named::ArrowRight => Some(Motion::Right), + key::Named::ArrowUp => Some(Motion::Up), + key::Named::ArrowDown => Some(Motion::Down), + key::Named::Home => Some(Motion::Home), + key::Named::End => Some(Motion::End), + key::Named::PageUp => Some(Motion::PageUp), + key::Named::PageDown => Some(Motion::PageDown), + _ => None, + } +} + +mod platform { + use crate::core::keyboard; + + pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool { + if cfg!(target_os = "macos") { + modifiers.alt() + } else { + modifiers.control() + } + } +} diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 9e1fb796..c3dce8be 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -14,6 +14,7 @@ use editor::Editor; use crate::core::alignment; use crate::core::event::{self, Event}; use crate::core::keyboard; +use crate::core::keyboard::key; use crate::core::layout; use crate::core::mouse::{self, click}; use crate::core::renderer; @@ -238,6 +239,7 @@ where layout: Layout<'_>, cursor: mouse::Cursor, value: Option<&Value>, + viewport: &Rectangle, ) { draw( renderer, @@ -250,6 +252,7 @@ where self.is_secure, self.icon.as_ref(), &self.style, + viewport, ); } } @@ -281,12 +284,11 @@ where } } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: Length::Shrink, + } } fn layout( @@ -362,7 +364,7 @@ where _style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { draw( renderer, @@ -375,6 +377,7 @@ where self.is_secure, self.icon.as_ref(), &self.style, + viewport, ); } @@ -503,14 +506,11 @@ where { 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(text_size)); + let height = line_height.to_absolute(text_size); - let text_bounds = limits.resolve(Size::ZERO); + let limits = limits.width(width).shrink(padding); + let text_bounds = limits.resolve(width, height, Size::ZERO); let placeholder_text = Text { font, @@ -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,45 +545,45 @@ 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(); - let mut text_node = layout::Node::new( - text_bounds - Size::new(icon_width + icon.spacing, 0.0), - ); - - let mut icon_node = - layout::Node::new(Size::new(icon_width, text_bounds.height)); - - match icon.side { - Side::Left => { - text_node.move_to(Point::new( + let (text_position, icon_position) = match icon.side { + Side::Left => ( + Point::new( padding.left + icon_width + icon.spacing, padding.top, - )); - - icon_node.move_to(Point::new(padding.left, padding.top)); - } - Side::Right => { - text_node.move_to(Point::new(padding.left, padding.top)); - - icon_node.move_to(Point::new( + ), + Point::new(padding.left, padding.top), + ), + Side::Right => ( + Point::new(padding.left, padding.top), + Point::new( padding.left + text_bounds.width - icon_width, padding.top, - )); - } + ), + ), }; + let text_node = layout::Node::new( + text_bounds - Size::new(icon_width + icon.spacing, 0.0), + ) + .move_to(text_position); + + let icon_node = + layout::Node::new(Size::new(icon_width, text_bounds.height)) + .move_to(icon_position); + layout::Node::with_children( - text_bounds.pad(padding), + text_bounds.expand(padding), vec![text_node, icon_node], ) } else { - let mut text = layout::Node::new(text_bounds); - text.move_to(Point::new(padding.left, padding.top)); + let text = layout::Node::new(text_bounds) + .move_to(Point::new(padding.left, padding.top)); - layout::Node::with_children(text_bounds.pad(padding), vec![text]) + layout::Node::with_children(text_bounds.expand(padding), vec![text]) } } @@ -752,34 +749,7 @@ where return event::Status::Captured; } } - Event::Keyboard(keyboard::Event::CharacterReceived(c)) => { - let state = state(); - - if let Some(focus) = &mut state.is_focused { - let Some(on_input) = on_input else { - return event::Status::Ignored; - }; - - if state.is_pasting.is_none() - && !state.keyboard_modifiers.command() - && !c.is_control() - { - let mut editor = Editor::new(value, &mut state.cursor); - - editor.insert(c); - - let message = (on_input)(editor.contents()); - shell.publish(message); - - focus.updated_at = Instant::now(); - - update_cache(state, value); - - return event::Status::Captured; - } - } - } - Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => { + Event::Keyboard(keyboard::Event::KeyPressed { key, text, .. }) => { let state = state(); if let Some(focus) = &mut state.is_focused { @@ -790,14 +760,13 @@ where let modifiers = state.keyboard_modifiers; focus.updated_at = Instant::now(); - match key_code { - keyboard::KeyCode::Enter - | keyboard::KeyCode::NumpadEnter => { + match key.as_ref() { + keyboard::Key::Named(key::Named::Enter) => { if let Some(on_submit) = on_submit.clone() { shell.publish(on_submit); } } - keyboard::KeyCode::Backspace => { + keyboard::Key::Named(key::Named::Backspace) => { if platform::is_jump_modifier_pressed(modifiers) && state.cursor.selection(value).is_none() { @@ -817,7 +786,7 @@ where update_cache(state, value); } - keyboard::KeyCode::Delete => { + keyboard::Key::Named(key::Named::Delete) => { if platform::is_jump_modifier_pressed(modifiers) && state.cursor.selection(value).is_none() { @@ -839,7 +808,7 @@ where update_cache(state, value); } - keyboard::KeyCode::Left => { + keyboard::Key::Named(key::Named::ArrowLeft) => { if platform::is_jump_modifier_pressed(modifiers) && !is_secure { @@ -854,7 +823,7 @@ where state.cursor.move_left(value); } } - keyboard::KeyCode::Right => { + keyboard::Key::Named(key::Named::ArrowRight) => { if platform::is_jump_modifier_pressed(modifiers) && !is_secure { @@ -869,7 +838,7 @@ where state.cursor.move_right(value); } } - keyboard::KeyCode::Home => { + keyboard::Key::Named(key::Named::Home) => { if modifiers.shift() { state .cursor @@ -878,7 +847,7 @@ where state.cursor.move_to(0); } } - keyboard::KeyCode::End => { + keyboard::Key::Named(key::Named::End) => { if modifiers.shift() { state.cursor.select_range( state.cursor.start(value), @@ -888,7 +857,7 @@ where state.cursor.move_to(value.len()); } } - keyboard::KeyCode::C + keyboard::Key::Character("c") if state.keyboard_modifiers.command() => { if let Some((start, end)) = @@ -898,7 +867,7 @@ where .write(value.select(start, end).to_string()); } } - keyboard::KeyCode::X + keyboard::Key::Character("x") if state.keyboard_modifiers.command() => { if let Some((start, end)) = @@ -916,7 +885,7 @@ where update_cache(state, value); } - keyboard::KeyCode::V => { + keyboard::Key::Character("v") => { if state.keyboard_modifiers.command() && !state.keyboard_modifiers.alt() { @@ -953,12 +922,12 @@ where state.is_pasting = None; } } - keyboard::KeyCode::A + keyboard::Key::Character("a") if state.keyboard_modifiers.command() => { state.cursor.select_all(value); } - keyboard::KeyCode::Escape => { + keyboard::Key::Named(key::Named::Escape) => { state.is_focused = None; state.is_dragging = false; state.is_pasting = None; @@ -966,28 +935,55 @@ where state.keyboard_modifiers = keyboard::Modifiers::default(); } - keyboard::KeyCode::Tab - | keyboard::KeyCode::Up - | keyboard::KeyCode::Down => { + keyboard::Key::Named( + key::Named::Tab + | key::Named::ArrowUp + | key::Named::ArrowDown, + ) => { return event::Status::Ignored; } - _ => {} + _ => { + if let Some(text) = text { + let c = text.chars().next().unwrap_or_default(); + + if state.is_pasting.is_none() + && !state.keyboard_modifiers.command() + && !c.is_control() + { + let mut editor = + Editor::new(value, &mut state.cursor); + + editor.insert(c); + + let message = (on_input)(editor.contents()); + shell.publish(message); + + focus.updated_at = Instant::now(); + + update_cache(state, value); + + return event::Status::Captured; + } + } + } } return event::Status::Captured; } } - Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => { + Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { let state = state(); if state.is_focused.is_some() { - match key_code { - keyboard::KeyCode::V => { + match key.as_ref() { + keyboard::Key::Character("v") => { state.is_pasting = None; } - keyboard::KeyCode::Tab - | keyboard::KeyCode::Up - | keyboard::KeyCode::Down => { + keyboard::Key::Named( + key::Named::Tab + | key::Named::ArrowUp + | key::Named::ArrowDown, + ) => { return event::Status::Ignored; } _ => {} @@ -1003,14 +999,14 @@ where state.keyboard_modifiers = modifiers; } - Event::Window(window::Event::Unfocused) => { + 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) => { + Event::Window(_, window::Event::Focused) => { let state = state(); if let Some(focus) = &mut state.is_focused { @@ -1020,7 +1016,7 @@ where shell.request_redraw(window::RedrawRequest::NextFrame); } } - Event::Window(window::Event::RedrawRequested(now)) => { + Event::Window(_, window::Event::RedrawRequested(now)) => { let state = state(); if let Some(focus) = &mut state.is_focused { @@ -1058,6 +1054,7 @@ pub fn draw<Renderer>( is_secure: bool, icon: Option<&Icon<Renderer::Font>>, style: &<Renderer::Theme as StyleSheet>::Style, + viewport: &Rectangle, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -1099,6 +1096,7 @@ pub fn draw<Renderer>( &state.icon, icon_layout.bounds().center(), appearance.icon_color, + *viewport, ); } @@ -1192,11 +1190,11 @@ pub fn draw<Renderer>( (None, 0.0) }; - let text_width = state.value.min_width(); - - let render = |renderer: &mut Renderer| { + let draw = |renderer: &mut Renderer, viewport| { if let Some((cursor, color)) = cursor { - renderer.fill_quad(cursor, color); + renderer.with_translation(Vector::new(-offset, 0.0), |renderer| { + renderer.fill_quad(cursor, color); + }); } else { renderer.with_translation(Vector::ZERO, |_| {}); } @@ -1207,7 +1205,8 @@ pub fn draw<Renderer>( } else { &state.value }, - Point::new(text_bounds.x, text_bounds.center_y()), + Point::new(text_bounds.x, text_bounds.center_y()) + - Vector::new(offset, 0.0), if text.is_empty() { theme.placeholder_color(style) } else if is_disabled { @@ -1215,15 +1214,14 @@ pub fn draw<Renderer>( } else { theme.value_color(style) }, + viewport, ); }; - if text_width > text_bounds.width { - renderer.with_layer(text_bounds, |renderer| { - renderer.with_translation(Vector::new(-offset, 0.0), render); - }); + if cursor.is_some() { + renderer.with_layer(text_bounds, |renderer| draw(renderer, *viewport)); } else { - render(renderer); + draw(renderer, text_bounds); } } @@ -1461,7 +1459,7 @@ fn replace_paragraph<Renderer>( 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(), diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 476c8330..941159ea 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -168,12 +168,11 @@ where tree::State::new(widget::text::State::<Renderer::Paragraph>::default()) } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink + fn size(&self) -> Size<Length> { + Size { + width: self.width, + height: Length::Shrink, + } } fn layout( @@ -266,7 +265,7 @@ where style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { /// Makes sure that the border radius of the toggler looks good at every size. const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0; @@ -287,6 +286,7 @@ where label_layout, tree.state.downcast_ref(), crate::text::Appearance::default(), + viewport, ); } diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index b041d2e9..d09a9255 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -64,6 +64,12 @@ where self } + /// Sets the [`text::Shaping`] strategy of the [`Tooltip`]. + pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { + self.tooltip = self.tooltip.shaping(shaping); + self + } + /// Sets the font of the [`Tooltip`]. /// /// [`Font`]: Renderer::Font @@ -125,12 +131,8 @@ where widget::tree::Tag::of::<State>() } - fn width(&self) -> Length { - self.content.as_widget().width() - } - - fn height(&self) -> Length { - self.content.as_widget().height() + fn size(&self) -> Size<Length> { + self.content.as_widget().size() } fn layout( @@ -157,11 +159,19 @@ where ) -> event::Status { let state = tree.state.downcast_mut::<State>(); + let was_idle = *state == State::Idle; + *state = cursor .position_over(layout.bounds()) .map(|cursor_position| State::Hovered { cursor_position }) .unwrap_or_default(); + let is_idle = *state == State::Idle; + + if was_idle != is_idle { + shell.invalidate_layout(); + } + self.content.as_widget_mut().on_event( &mut tree.children[0], event, @@ -289,7 +299,7 @@ pub enum Position { Right, } -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] enum State { #[default] Idle, @@ -325,6 +335,7 @@ where renderer: &Renderer, bounds: Size, position: Point, + _translation: Vector, ) -> layout::Node { let viewport = Rectangle::with_size(bounds); @@ -338,7 +349,7 @@ where .then(|| viewport.size()) .unwrap_or(Size::INFINITY), ) - .pad(Padding::new(self.padding)), + .shrink(Padding::new(self.padding)), ); let text_bounds = text_layout.bounds(); diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 01d3359c..a3029d76 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -156,12 +156,11 @@ where tree::State::new(State::new()) } - fn width(&self) -> Length { - Length::Shrink - } - - fn height(&self) -> Length { - self.height + fn size(&self) -> Size<Length> { + Size { + width: Length::Shrink, + height: self.height, + } } fn layout( @@ -170,10 +169,7 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) + layout::atomic(limits, self.width, self.height) } fn on_event( |