diff options
Diffstat (limited to 'native/src/widget')
-rw-r--r-- | native/src/widget/button.rs | 10 | ||||
-rw-r--r-- | native/src/widget/checkbox.rs | 2 | ||||
-rw-r--r-- | native/src/widget/column.rs | 9 | ||||
-rw-r--r-- | native/src/widget/container.rs | 8 | ||||
-rw-r--r-- | native/src/widget/helpers.rs | 27 | ||||
-rw-r--r-- | native/src/widget/image/viewer.rs | 13 | ||||
-rw-r--r-- | native/src/widget/pane_grid.rs | 15 | ||||
-rw-r--r-- | native/src/widget/pane_grid/content.rs | 11 | ||||
-rw-r--r-- | native/src/widget/pane_grid/title_bar.rs | 11 | ||||
-rw-r--r-- | native/src/widget/pick_list.rs | 4 | ||||
-rw-r--r-- | native/src/widget/progress_bar.rs | 6 | ||||
-rw-r--r-- | native/src/widget/radio.rs | 4 | ||||
-rw-r--r-- | native/src/widget/row.rs | 9 | ||||
-rw-r--r-- | native/src/widget/rule.rs | 2 | ||||
-rw-r--r-- | native/src/widget/scrollable.rs | 20 | ||||
-rw-r--r-- | native/src/widget/slider.rs | 6 | ||||
-rw-r--r-- | native/src/widget/svg.rs | 54 | ||||
-rw-r--r-- | native/src/widget/text_input.rs | 19 | ||||
-rw-r--r-- | native/src/widget/toggler.rs | 26 | ||||
-rw-r--r-- | native/src/widget/tooltip.rs | 4 | ||||
-rw-r--r-- | native/src/widget/vertical_slider.rs | 470 |
21 files changed, 640 insertions, 90 deletions
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index fa5da24b..b4276317 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -169,12 +169,14 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { operation.container(None, &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), + renderer, operation, ); }); @@ -260,12 +262,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, @@ -393,7 +395,7 @@ where y: bounds.y + styling.shadow_offset.y, ..bounds }, - border_radius: styling.border_radius, + border_radius: styling.border_radius.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -404,7 +406,7 @@ where renderer.fill_quad( renderer::Quad { bounds, - border_radius: styling.border_radius, + border_radius: styling.border_radius.into(), border_width: styling.border_width, border_color: styling.border_color, }, diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 77d639a9..bec5c448 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -236,7 +236,7 @@ where renderer.fill_quad( renderer::Quad { bounds, - border_radius: custom_style.border_radius, + border_radius: custom_style.border_radius.into(), border_width: custom_style.border_width, border_color: custom_style.border_color, }, diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index a8b0f183..f2ef132a 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -147,6 +147,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { operation.container(None, &mut |operation| { @@ -155,7 +156,9 @@ where .zip(&mut tree.children) .zip(layout.children()) .for_each(|((child, state), layout)| { - child.as_widget().operate(state, layout, operation); + child + .as_widget() + .operate(state, layout, renderer, operation); }) }); } @@ -242,12 +245,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - overlay::from_children(&self.children, tree, layout, renderer) + overlay::from_children(&mut self.children, tree, layout, renderer) } } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 9d3e4d9b..cdf1c859 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -169,12 +169,14 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { operation.container(None, &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), + renderer, operation, ); }); @@ -248,12 +250,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, @@ -321,7 +323,7 @@ pub fn draw_background<Renderer>( renderer.fill_quad( renderer::Quad { bounds, - border_radius: appearance.border_radius, + border_radius: appearance.border_radius.into(), border_width: appearance.border_width, border_color: appearance.border_color, }, diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 3bce9e60..5b241f83 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -162,7 +162,7 @@ where Renderer: crate::text::Renderer, Renderer::Theme: widget::toggler::StyleSheet, { - widget::Toggler::new(is_checked, label, f) + widget::Toggler::new(label, is_checked, f) } /// Creates a new [`TextInput`]. @@ -198,6 +198,23 @@ where widget::Slider::new(range, value, on_change) } +/// Creates a new [`VerticalSlider`]. +/// +/// [`VerticalSlider`]: widget::VerticalSlider +pub fn vertical_slider<'a, T, Message, Renderer>( + range: std::ops::RangeInclusive<T>, + value: T, + on_change: impl Fn(T) -> Message + 'a, +) -> widget::VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + From<u8> + std::cmp::PartialOrd, + Message: Clone, + Renderer: crate::Renderer, + Renderer::Theme: widget::slider::StyleSheet, +{ + widget::VerticalSlider::new(range, value, on_change) +} + /// Creates a new [`PickList`]. /// /// [`PickList`]: widget::PickList @@ -285,6 +302,12 @@ where /// /// [`Svg`]: widget::Svg /// [`Handle`]: widget::svg::Handle -pub fn svg(handle: impl Into<widget::svg::Handle>) -> widget::Svg { +pub fn svg<Renderer>( + handle: impl Into<widget::svg::Handle>, +) -> widget::Svg<Renderer> +where + Renderer: crate::svg::Renderer, + Renderer::Theme: widget::svg::StyleSheet, +{ widget::Svg::new(handle) } diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 9c83287e..fdbd3216 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -170,8 +170,7 @@ where } else { state.scale / (1.0 + self.scale_step) }) - .max(self.min_scale) - .min(self.max_scale); + .clamp(self.min_scale, self.max_scale); let image_size = image_size( renderer, @@ -251,16 +250,14 @@ where let x = if bounds.width < image_size.width { (state.starting_offset.x - delta.x) - .min(hidden_width) - .max(-hidden_width) + .clamp(-hidden_width, hidden_width) } else { 0.0 }; let y = if bounds.height < image_size.height { (state.starting_offset.y - delta.y) - .min(hidden_height) - .max(-hidden_height) + .clamp(-hidden_height, hidden_height) } else { 0.0 }; @@ -374,8 +371,8 @@ impl State { (image_size.height - bounds.height / 2.0).max(0.0).round(); Vector::new( - self.current_offset.x.min(hidden_width).max(-hidden_width), - self.current_offset.y.min(hidden_height).max(-hidden_height), + self.current_offset.x.clamp(-hidden_width, hidden_width), + self.current_offset.y.clamp(-hidden_height, hidden_height), ) } diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 8f9065b0..f8dbab74 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -6,7 +6,7 @@ //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, //! drag and drop, and hotkey support. //! -//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.5/examples/pane_grid +//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.6/examples/pane_grid mod axis; mod configuration; mod content; @@ -294,6 +294,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { operation.container(None, &mut |operation| { @@ -302,7 +303,7 @@ where .zip(&mut tree.children) .zip(layout.children()) .for_each(|(((_pane, content), state), layout)| { - content.operate(state, layout, operation); + content.operate(state, layout, renderer, operation); }) }); } @@ -444,13 +445,13 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'_, Message, Renderer>> { self.contents - .iter() + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|(((_, pane), tree), layout)| { @@ -630,13 +631,13 @@ pub fn update<'a, Message, T: Draggable>( let position = cursor_position.y - bounds.y - rectangle.y; - (position / rectangle.height).max(0.1).min(0.9) + (position / rectangle.height).clamp(0.1, 0.9) } Axis::Vertical => { let position = cursor_position.x - bounds.x - rectangle.x; - (position / rectangle.width).max(0.1).min(0.9) + (position / rectangle.width).clamp(0.1, 0.9) } }; @@ -877,7 +878,7 @@ pub fn draw<Renderer, T>( height: split_region.height, }, }, - border_radius: 0.0, + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 5e843cff..c9b0df07 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -187,6 +187,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { let body_layout = if let Some(title_bar) = &self.title_bar { @@ -195,6 +196,7 @@ where title_bar.operate( &mut tree.children[1], children.next().unwrap(), + renderer, operation, ); @@ -206,6 +208,7 @@ where self.body.as_widget().operate( &mut tree.children[0], body_layout, + renderer, operation, ); } @@ -305,12 +308,12 @@ where } pub(crate) fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - if let Some(title_bar) = self.title_bar.as_ref() { + if let Some(title_bar) = self.title_bar.as_mut() { let mut children = layout.children(); let title_bar_layout = children.next()?; @@ -321,14 +324,14 @@ where match title_bar.overlay(title_bar_state, title_bar_layout, renderer) { Some(overlay) => Some(overlay), - None => self.body.as_widget().overlay( + None => self.body.as_widget_mut().overlay( body_state, children.next()?, renderer, ), } } else { - self.body.as_widget().overlay( + self.body.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 115f6270..ea0969aa 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -261,6 +261,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { let mut children = layout.children(); @@ -282,6 +283,7 @@ where controls.as_widget().operate( &mut tree.children[1], controls_layout, + renderer, operation, ) }; @@ -290,6 +292,7 @@ where self.content.as_widget().operate( &mut tree.children[0], title_layout, + renderer, operation, ) } @@ -395,7 +398,7 @@ where } pub(crate) fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -415,13 +418,13 @@ where let controls_state = states.next().unwrap(); content - .as_widget() + .as_widget_mut() .overlay(title_state, title_layout, renderer) .or_else(move || { - controls.as_ref().and_then(|controls| { + controls.as_mut().and_then(|controls| { let controls_layout = children.next()?; - controls.as_widget().overlay( + controls.as_widget_mut().overlay( controls_state, controls_layout, renderer, diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 43ae7ebb..c2853314 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -282,7 +282,7 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, _renderer: &Renderer, @@ -600,7 +600,7 @@ pub fn draw<T, Renderer>( bounds, border_color: style.border_color, border_width: style.border_width, - border_radius: style.border_radius, + border_radius: style.border_radius.into(), }, style.background, ); diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index b053d959..7d5d5be5 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -47,7 +47,7 @@ where /// * the current value of the [`ProgressBar`] pub fn new(range: RangeInclusive<f32>, value: f32) -> Self { ProgressBar { - value: value.max(*range.start()).min(*range.end()), + value: value.clamp(*range.start(), *range.end()), range, width: Length::Fill, height: None, @@ -129,7 +129,7 @@ where renderer.fill_quad( renderer::Quad { bounds: Rectangle { ..bounds }, - border_radius: style.border_radius, + border_radius: style.border_radius.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -143,7 +143,7 @@ where width: active_progress_width, ..bounds }, - border_radius: style.border_radius, + border_radius: style.border_radius.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 743689c7..b95ccc5b 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -245,7 +245,7 @@ where renderer.fill_quad( renderer::Quad { bounds, - border_radius: size / 2.0, + border_radius: (size / 2.0).into(), border_width: custom_style.border_width, border_color: custom_style.border_color, }, @@ -261,7 +261,7 @@ where width: bounds.width - dot_size, height: bounds.height - dot_size, }, - border_radius: dot_size / 2.0, + border_radius: (dot_size / 2.0).into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index eda7c2d3..108e98e4 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -134,6 +134,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { operation.container(None, &mut |operation| { @@ -142,7 +143,9 @@ where .zip(&mut tree.children) .zip(layout.children()) .for_each(|((child, state), layout)| { - child.as_widget().operate(state, layout, operation); + child + .as_widget() + .operate(state, layout, renderer, operation); }) }); } @@ -229,12 +232,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - overlay::from_children(&self.children, tree, layout, renderer) + overlay::from_children(&mut self.children, tree, layout, renderer) } } diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs index e44d8d99..2dc7b6f0 100644 --- a/native/src/widget/rule.rs +++ b/native/src/widget/rule.rs @@ -123,7 +123,7 @@ where renderer.fill_quad( renderer::Quad { bounds, - border_radius: style.radius, + border_radius: style.radius.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 32ec6eb3..20780f89 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -164,6 +164,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { let state = tree.state.downcast_mut::<State>(); @@ -174,6 +175,7 @@ where self.content.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), + renderer, operation, ); }); @@ -276,13 +278,13 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { self.content - .as_widget() + .as_widget_mut() .overlay( &mut tree.children[0], layout.children().next().unwrap(), @@ -704,7 +706,7 @@ pub fn draw<Renderer>( renderer.fill_quad( renderer::Quad { bounds: scrollbar.bounds, - border_radius: style.border_radius, + border_radius: style.border_radius.into(), border_width: style.border_width, border_color: style.border_color, }, @@ -714,14 +716,13 @@ pub fn draw<Renderer>( ); } - if is_mouse_over - || state.is_scroller_grabbed() - || is_scrollbar_visible + if (is_mouse_over || state.is_scroller_grabbed()) + && is_scrollbar_visible { renderer.fill_quad( renderer::Quad { bounds: scrollbar.scroller.bounds, - border_radius: style.scroller.border_radius, + border_radius: style.scroller.border_radius.into(), border_width: style.scroller.border_width, border_color: style.scroller.border_color, }, @@ -882,8 +883,7 @@ impl State { self.offset = Offset::Absolute( (self.offset.absolute(bounds, content_bounds) - delta_y) - .max(0.0) - .min((content_bounds.height - bounds.height) as f32), + .clamp(0.0, content_bounds.height - bounds.height), ); } @@ -906,7 +906,7 @@ impl State { /// `0` represents scrollbar at the top, while `1` represents scrollbar at /// the bottom. pub fn snap_to(&mut self, percentage: f32) { - self.offset = Offset::Relative(percentage.max(0.0).min(1.0)); + self.offset = Offset::Relative(percentage.clamp(0.0, 1.0)); } /// Unsnaps the current scroll position, if snapped, given the bounds of the diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index fd9160a4..87030a4d 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -380,7 +380,7 @@ pub fn draw<T, R>( width: bounds.width, height: 2.0, }, - border_radius: 0.0, + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -395,7 +395,7 @@ pub fn draw<T, R>( width: bounds.width, height: 2.0, }, - border_radius: 0.0, + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -435,7 +435,7 @@ pub fn draw<T, R>( width: handle_width, height: handle_height, }, - border_radius: handle_border_radius, + border_radius: handle_border_radius.into(), border_width: style.handle.border_width, border_color: style.handle.border_color, }, diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index 1015ed0a..f83f5acf 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -9,6 +9,7 @@ use crate::{ use std::path::PathBuf; +pub use iced_style::svg::{Appearance, StyleSheet}; pub use svg::Handle; /// A vector graphics image. @@ -17,15 +18,24 @@ pub use svg::Handle; /// /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. -#[derive(Debug, Clone)] -pub struct Svg { +#[allow(missing_debug_implementations)] +pub struct Svg<Renderer> +where + Renderer: svg::Renderer, + Renderer::Theme: StyleSheet, +{ handle: Handle, width: Length, height: Length, content_fit: ContentFit, + style: <Renderer::Theme as StyleSheet>::Style, } -impl Svg { +impl<Renderer> Svg<Renderer> +where + Renderer: svg::Renderer, + Renderer::Theme: StyleSheet, +{ /// Creates a new [`Svg`] from the given [`Handle`]. pub fn new(handle: impl Into<Handle>) -> Self { Svg { @@ -33,22 +43,26 @@ impl Svg { width: Length::Fill, height: Length::Shrink, content_fit: ContentFit::Contain, + style: Default::default(), } } /// Creates a new [`Svg`] that will display the contents of the file at the /// provided path. + #[must_use] pub fn from_path(path: impl Into<PathBuf>) -> Self { Self::new(Handle::from_path(path)) } /// Sets the width of the [`Svg`]. + #[must_use] pub fn width(mut self, width: Length) -> Self { self.width = width; self } /// Sets the height of the [`Svg`]. + #[must_use] pub fn height(mut self, height: Length) -> Self { self.height = height; self @@ -57,17 +71,29 @@ impl Svg { /// Sets the [`ContentFit`] of the [`Svg`]. /// /// Defaults to [`ContentFit::Contain`] + #[must_use] pub fn content_fit(self, content_fit: ContentFit) -> Self { Self { content_fit, ..self } } + + /// Sets the style variant of this [`Svg`]. + #[must_use] + pub fn style( + mut self, + style: <Renderer::Theme as StyleSheet>::Style, + ) -> Self { + self.style = style; + self + } } -impl<Message, Renderer> Widget<Message, Renderer> for Svg +impl<Message, Renderer> Widget<Message, Renderer> for Svg<Renderer> where Renderer: svg::Renderer, + Renderer::Theme: iced_style::svg::StyleSheet, { fn width(&self) -> Length { self.width @@ -114,7 +140,7 @@ where &self, _state: &Tree, renderer: &mut Renderer, - _theme: &Renderer::Theme, + theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, _cursor_position: Point, @@ -138,7 +164,13 @@ where ..bounds }; - renderer.draw(self.handle.clone(), drawing_bounds + offset) + let appearance = theme.appearance(&self.style); + + renderer.draw( + self.handle.clone(), + appearance.color, + drawing_bounds + offset, + ); }; if adjusted_fit.width > bounds.width @@ -146,16 +178,18 @@ where { renderer.with_layer(bounds, render); } else { - render(renderer) + render(renderer); } } } -impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer> +impl<'a, Message, Renderer> From<Svg<Renderer>> + for Element<'a, Message, Renderer> where - Renderer: svg::Renderer, + Renderer: svg::Renderer + 'a, + Renderer::Theme: iced_style::svg::StyleSheet, { - fn from(icon: Svg) -> Element<'a, Message, Renderer> { + fn from(icon: Svg<Renderer>) -> Element<'a, Message, Renderer> { Element::new(icon) } } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 14e7e1b7..8b4514e3 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -228,6 +228,7 @@ where &self, tree: &mut Tree, _layout: Layout<'_>, + _renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { let state = tree.state.downcast_mut::<State>(); @@ -453,9 +454,17 @@ where ) } else { None - }; + } + .unwrap_or(0); - state.cursor.move_to(position.unwrap_or(0)); + if state.keyboard_modifiers.shift() { + state.cursor.select_range( + state.cursor.start(value), + position, + ); + } else { + state.cursor.move_to(position); + } state.is_dragging = true; } click::Kind::Double => { @@ -801,7 +810,7 @@ pub fn draw<Renderer>( renderer.fill_quad( renderer::Quad { bounds, - border_radius: appearance.border_radius, + border_radius: appearance.border_radius.into(), border_width: appearance.border_width, border_color: appearance.border_color, }, @@ -833,7 +842,7 @@ pub fn draw<Renderer>( width: 1.0, height: text_bounds.height, }, - border_radius: 0.0, + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -877,7 +886,7 @@ pub fn draw<Renderer>( width, height: text_bounds.height, }, - border_radius: 0.0, + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 99a56ea8..f0a944a3 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -24,9 +24,9 @@ pub use iced_style::toggler::{Appearance, StyleSheet}; /// TogglerToggled(bool), /// } /// -/// let is_active = true; +/// let is_toggled = true; /// -/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b)); +/// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b)); /// ``` #[allow(missing_debug_implementations)] pub struct Toggler<'a, Message, Renderer> @@ -34,7 +34,7 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { - is_active: bool, + is_toggled: bool, on_toggle: Box<dyn Fn(bool) -> Message + 'a>, label: Option<String>, width: Length, @@ -63,15 +63,15 @@ where /// will receive the new state of the [`Toggler`] and must produce a /// `Message`. pub fn new<F>( - is_active: bool, label: impl Into<Option<String>>, + is_toggled: bool, f: F, ) -> Self where F: 'a + Fn(bool) -> Message, { Toggler { - is_active, + is_toggled, on_toggle: Box::new(f), label: label.into(), width: Length::Fill, @@ -193,7 +193,7 @@ where let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { - shell.publish((self.on_toggle)(!self.is_active)); + shell.publish((self.on_toggle)(!self.is_toggled)); event::Status::Captured } else { @@ -260,13 +260,13 @@ where let is_mouse_over = bounds.contains(cursor_position); let style = if is_mouse_over { - theme.hovered(&self.style, self.is_active) + theme.hovered(&self.style, self.is_toggled) } else { - theme.active(&self.style, self.is_active) + theme.active(&self.style, self.is_toggled) }; - let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO; - let space = SPACE_RATIO * bounds.height as f32; + let border_radius = bounds.height / BORDER_RADIUS_RATIO; + let space = SPACE_RATIO * bounds.height; let toggler_background_bounds = Rectangle { x: bounds.x + space, @@ -278,7 +278,7 @@ where renderer.fill_quad( renderer::Quad { bounds: toggler_background_bounds, - border_radius, + border_radius: border_radius.into(), border_width: 1.0, border_color: style .background_border @@ -289,7 +289,7 @@ where let toggler_foreground_bounds = Rectangle { x: bounds.x - + if self.is_active { + + if self.is_toggled { bounds.width - 2.0 * space - (bounds.height - (4.0 * space)) } else { 2.0 * space @@ -302,7 +302,7 @@ where renderer.fill_quad( renderer::Quad { bounds: toggler_foreground_bounds, - border_radius, + border_radius: border_radius.into(), border_width: 1.0, border_color: style .foreground_border diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 9347a886..084dc269 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -221,12 +221,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, diff --git a/native/src/widget/vertical_slider.rs b/native/src/widget/vertical_slider.rs new file mode 100644 index 00000000..28e8405c --- /dev/null +++ b/native/src/widget/vertical_slider.rs @@ -0,0 +1,470 @@ +//! Display an interactive selector of a single value from a range of values. +//! +//! A [`VerticalSlider`] has some local [`State`]. +use std::ops::RangeInclusive; + +pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; + +use crate::event::{self, Event}; +use crate::widget::tree::{self, Tree}; +use crate::{ + layout, mouse, renderer, touch, Background, Clipboard, Color, Element, + Layout, Length, Point, Rectangle, Shell, Size, Widget, +}; + +/// An vertical bar and a handle that selects a single value from a range of +/// values. +/// +/// A [`VerticalSlider`] will try to fill the vertical space of its container. +/// +/// The [`VerticalSlider`] range of numeric values is generic and its step size defaults +/// to 1 unit. +/// +/// # Example +/// ``` +/// # use iced_native::widget::vertical_slider; +/// # use iced_native::renderer::Null; +/// # +/// # type VerticalSlider<'a, T, Message> = vertical_slider::VerticalSlider<'a, T, Message, Null>; +/// # +/// #[derive(Clone)] +/// pub enum Message { +/// SliderChanged(f32), +/// } +/// +/// let value = 50.0; +/// +/// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged); +/// ``` +#[allow(missing_debug_implementations)] +pub struct VerticalSlider<'a, T, Message, Renderer> +where + Renderer: crate::Renderer, + Renderer::Theme: StyleSheet, +{ + range: RangeInclusive<T>, + step: T, + value: T, + on_change: Box<dyn Fn(T) -> Message + 'a>, + on_release: Option<Message>, + width: u16, + height: Length, + style: <Renderer::Theme as StyleSheet>::Style, +} + +impl<'a, T, Message, Renderer> VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + From<u8> + std::cmp::PartialOrd, + Message: Clone, + Renderer: crate::Renderer, + Renderer::Theme: StyleSheet, +{ + /// The default width of a [`VerticalSlider`]. + pub const DEFAULT_WIDTH: u16 = 22; + + /// Creates a new [`VerticalSlider`]. + /// + /// It expects: + /// * an inclusive range of possible values + /// * the current value of the [`VerticalSlider`] + /// * a function that will be called when the [`VerticalSlider`] is dragged. + /// It receives the new value of the [`VerticalSlider`] and must produce a + /// `Message`. + pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self + where + F: 'a + Fn(T) -> Message, + { + let value = if value >= *range.start() { + value + } else { + *range.start() + }; + + let value = if value <= *range.end() { + value + } else { + *range.end() + }; + + VerticalSlider { + value, + range, + step: T::from(1), + on_change: Box::new(on_change), + on_release: None, + width: Self::DEFAULT_WIDTH, + height: Length::Fill, + style: Default::default(), + } + } + + /// Sets the release message of the [`VerticalSlider`]. + /// This is called when the mouse is released from the slider. + /// + /// Typically, the user's interaction with the slider is finished when this message is produced. + /// This is useful if you need to spawn a long-running task from the slider's result, where + /// the default on_change message could create too many events. + pub fn on_release(mut self, on_release: Message) -> Self { + self.on_release = Some(on_release); + self + } + + /// Sets the width of the [`VerticalSlider`]. + pub fn width(mut self, width: u16) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`VerticalSlider`]. + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the style of the [`VerticalSlider`]. + pub fn style( + mut self, + style: impl Into<<Renderer::Theme as StyleSheet>::Style>, + ) -> Self { + self.style = style.into(); + self + } + + /// Sets the step size of the [`VerticalSlider`]. + pub fn step(mut self, step: T) -> Self { + self.step = step; + self + } +} + +impl<'a, T, Message, Renderer> Widget<Message, Renderer> + for VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + Into<f64> + num_traits::FromPrimitive, + Message: Clone, + Renderer: crate::Renderer, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::<State>() + } + + fn state(&self) -> tree::State { + tree::State::new(State::new()) + } + + fn width(&self) -> Length { + Length::Shrink + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = + limits.width(Length::Units(self.width)).height(self.height); + + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + update( + event, + layout, + cursor_position, + shell, + tree.state.downcast_mut::<State>(), + &mut self.value, + &self.range, + self.step, + self.on_change.as_ref(), + &self.on_release, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + ) { + draw( + renderer, + layout, + cursor_position, + tree.state.downcast_ref::<State>(), + self.value, + &self.range, + theme, + &self.style, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + mouse_interaction( + layout, + cursor_position, + tree.state.downcast_ref::<State>(), + ) + } +} + +impl<'a, T, Message, Renderer> From<VerticalSlider<'a, T, Message, Renderer>> + for Element<'a, Message, Renderer> +where + T: 'a + Copy + Into<f64> + num_traits::FromPrimitive, + Message: 'a + Clone, + Renderer: 'a + crate::Renderer, + Renderer::Theme: StyleSheet, +{ + fn from( + slider: VerticalSlider<'a, T, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(slider) + } +} + +/// Processes an [`Event`] and updates the [`State`] of a [`VerticalSlider`] +/// accordingly. +pub fn update<Message, T>( + event: Event, + layout: Layout<'_>, + cursor_position: Point, + shell: &mut Shell<'_, Message>, + state: &mut State, + value: &mut T, + range: &RangeInclusive<T>, + step: T, + on_change: &dyn Fn(T) -> Message, + on_release: &Option<Message>, +) -> event::Status +where + T: Copy + Into<f64> + num_traits::FromPrimitive, + Message: Clone, +{ + let is_dragging = state.is_dragging; + + let mut change = || { + let bounds = layout.bounds(); + let new_value = if cursor_position.y >= bounds.y + bounds.height { + *range.start() + } else if cursor_position.y <= bounds.y { + *range.end() + } else { + let step = step.into(); + let start = (*range.start()).into(); + let end = (*range.end()).into(); + + let percent = 1.0 + - f64::from(cursor_position.y - bounds.y) + / f64::from(bounds.height); + + let steps = (percent * (end - start) / step).round(); + let value = steps * step + start; + + if let Some(value) = T::from_f64(value) { + value + } else { + return; + } + }; + + if ((*value).into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((on_change)(new_value)); + + *value = new_value; + } + }; + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if layout.bounds().contains(cursor_position) { + change(); + state.is_dragging = true; + + return event::Status::Captured; + } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + if is_dragging { + if let Some(on_release) = on_release.clone() { + shell.publish(on_release); + } + state.is_dragging = false; + + return event::Status::Captured; + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if is_dragging { + change(); + + return event::Status::Captured; + } + } + _ => {} + } + + event::Status::Ignored +} + +/// Draws a [`VerticalSlider`]. +pub fn draw<T, R>( + renderer: &mut R, + layout: Layout<'_>, + cursor_position: Point, + state: &State, + value: T, + range: &RangeInclusive<T>, + style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>, + style: &<R::Theme as StyleSheet>::Style, +) where + T: Into<f64> + Copy, + R: crate::Renderer, + R::Theme: StyleSheet, +{ + let bounds = layout.bounds(); + let is_mouse_over = bounds.contains(cursor_position); + + let style = if state.is_dragging { + style_sheet.dragging(style) + } else if is_mouse_over { + style_sheet.hovered(style) + } else { + style_sheet.active(style) + }; + + let rail_x = bounds.x + (bounds.width / 2.0).round(); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: rail_x - 1.0, + y: bounds.y, + width: 2.0, + height: bounds.height, + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + style.rail_colors.0, + ); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: rail_x + 1.0, + y: bounds.y, + width: 2.0, + height: bounds.height, + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Background::Color(style.rail_colors.1), + ); + + let (handle_width, handle_height, handle_border_radius) = match style + .handle + .shape + { + HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius), + HandleShape::Rectangle { + width, + border_radius, + } => (f32::from(width), bounds.width, border_radius), + }; + + let value = value.into() as f32; + let (range_start, range_end) = { + let (start, end) = range.clone().into_inner(); + + (start.into() as f32, end.into() as f32) + }; + + let handle_offset = if range_start >= range_end { + 0.0 + } else { + bounds.height * (value - range_end) / (range_start - range_end) + - handle_width / 2.0 + }; + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: rail_x - (handle_height / 2.0), + y: bounds.y + handle_offset.round(), + width: handle_height, + height: handle_width, + }, + border_radius: handle_border_radius.into(), + border_width: style.handle.border_width, + border_color: style.handle.border_color, + }, + style.handle.color, + ); +} + +/// Computes the current [`mouse::Interaction`] of a [`VerticalSlider`]. +pub fn mouse_interaction( + layout: Layout<'_>, + cursor_position: Point, + state: &State, +) -> mouse::Interaction { + let bounds = layout.bounds(); + let is_mouse_over = bounds.contains(cursor_position); + + if state.is_dragging { + mouse::Interaction::Grabbing + } else if is_mouse_over { + mouse::Interaction::Grab + } else { + mouse::Interaction::default() + } +} + +/// The local state of a [`VerticalSlider`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct State { + is_dragging: bool, +} + +impl State { + /// Creates a new [`State`]. + pub fn new() -> State { + State::default() + } +} |