diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml index 96dcc3b64c..d58a4febd3 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -58,12 +58,12 @@ body: attributes: label: Version description: | - We only offer support for the `0.4` release on crates.io and the `master` branch on this repository. Which version are you using? Please make sure you are using the latest patch available (e.g. run `cargo update`). + We only offer support for the latest release on crates.io and the `master` branch on this repository. Which version are you using? Please make sure you are using the latest patch available (e.g. run `cargo update`). - If you are using an older release, please upgrade to `0.4` before filing an issue. + If you are using an older release, please upgrade to the latest one before filing an issue. options: - master - - 0.4 + - 0.6 validations: required: true - type: dropdown diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 4e0820510e..4fe9151957 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -116,8 +116,8 @@ impl std::ops::Mul for Rectangle { fn mul(self, scale: f32) -> Self { Self { - x: self.x as f32 * scale, - y: self.y as f32 * scale, + x: self.x * scale, + y: self.y * scale, width: self.width * scale, height: self.height * scale, } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 2a8b3721f7..b0f1c96dba 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -492,8 +492,10 @@ mod grid { let old_scaling = self.scaling; let scaling = (self.scaling * (1.0 + y / 30.0)) - .max(Self::MIN_SCALING) - .min(Self::MAX_SCALING); + .clamp( + Self::MIN_SCALING, + Self::MAX_SCALING, + ); let translation = if let Some(cursor_to_center) = diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 2f20795c0a..5afafd0df2 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -325,11 +325,13 @@ mod modal { &self, state: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.base.as_widget().operate( &mut state.children[0], layout, + renderer, operation, ); } @@ -436,11 +438,13 @@ mod modal { fn operate( &mut self, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.content.as_widget().operate( self.tree, layout.children().next().unwrap(), + renderer, operation, ); } diff --git a/examples/slider/Cargo.toml b/examples/slider/Cargo.toml new file mode 100644 index 0000000000..112d7cff5e --- /dev/null +++ b/examples/slider/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "slider" +version = "0.1.0" +authors = ["Casper Rogild Storm"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/slider/README.md b/examples/slider/README.md new file mode 100644 index 0000000000..829d828506 --- /dev/null +++ b/examples/slider/README.md @@ -0,0 +1,14 @@ +## Slider + +A `Slider` is a bar and a handle that selects a single value from a range of values. +There exists both `Slider` and `VerticalSlider` depending on which orientation you need. + +
+ +
+ +You can run it with `cargo run`: + +``` +cargo run --package slider +``` diff --git a/examples/slider/sliders.gif b/examples/slider/sliders.gif new file mode 100644 index 0000000000..f906d05ab1 Binary files /dev/null and b/examples/slider/sliders.gif differ diff --git a/examples/slider/src/main.rs b/examples/slider/src/main.rs new file mode 100644 index 0000000000..6286d62581 --- /dev/null +++ b/examples/slider/src/main.rs @@ -0,0 +1,63 @@ +use iced::widget::{column, container, slider, text, vertical_slider}; +use iced::{Element, Length, Sandbox, Settings}; + +pub fn main() -> iced::Result { + Slider::run(Settings::default()) +} + +#[derive(Debug, Clone)] +pub enum Message { + SliderChanged(u8), +} + +pub struct Slider { + slider_value: u8, +} + +impl Sandbox for Slider { + type Message = Message; + + fn new() -> Slider { + Slider { slider_value: 50 } + } + + fn title(&self) -> String { + String::from("Slider - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::SliderChanged(value) => { + self.slider_value = value; + } + } + } + + fn view(&self) -> Element { + let value = self.slider_value; + + let h_slider = + container(slider(0..=100, value, Message::SliderChanged)) + .width(Length::Units(250)); + + let v_slider = + container(vertical_slider(0..=100, value, Message::SliderChanged)) + .height(Length::Units(200)); + + let text = text(format!("{value}")); + + container( + column![ + container(v_slider).width(Length::Fill).center_x(), + container(h_slider).width(Length::Fill).center_x(), + container(text).width(Length::Fill).center_x(), + ] + .spacing(25), + ) + .height(Length::Fill) + .width(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/glow/Cargo.toml b/glow/Cargo.toml index f69ce48a73..f586d24d96 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_glow" -version = "0.5.0" +version = "0.5.1" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "A glow renderer for iced" diff --git a/glow/src/image.rs b/glow/src/image.rs index c32b216291..521a01e7bc 100644 --- a/glow/src/image.rs +++ b/glow/src/image.rs @@ -192,7 +192,7 @@ impl Pipeline { } #[cfg(not(feature = "svg"))] - layer::Image::Vector { handle: _, bounds } => (None, bounds), + layer::Image::Vector { bounds, .. } => (None, bounds), }; unsafe { diff --git a/glutin/src/application.rs b/glutin/src/application.rs index f474cbba04..3e9d11f9ff 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -200,6 +200,7 @@ async fn run_instance( let mut cache = user_interface::Cache::default(); let mut state = application::State::new(&application, context.window()); let mut viewport_version = state.viewport_version(); + let mut should_exit = false; application::run_command( &application, @@ -209,6 +210,7 @@ async fn run_instance( init_command, &mut runtime, &mut clipboard, + &mut should_exit, &mut proxy, &mut debug, context.window(), @@ -271,6 +273,7 @@ async fn run_instance( &mut renderer, &mut runtime, &mut clipboard, + &mut should_exit, &mut proxy, &mut debug, &mut messages, @@ -281,8 +284,6 @@ async fn run_instance( // Update window state.synchronize(&application, context.window()); - let should_exit = application.should_exit(); - user_interface = ManuallyDrop::new(application::build_user_interface( &application, diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 3d7b8758bc..ad15d69d17 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -234,6 +234,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation, ) { struct MapOperation<'a, B> { @@ -274,6 +275,7 @@ where element.as_widget().operate( &mut tree.children[0], layout, + renderer, &mut MapOperation { operation }, ); }); diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs index 2611dd1092..ec35e8f076 100644 --- a/lazy/src/lazy.rs +++ b/lazy/src/lazy.rs @@ -130,12 +130,14 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.with_element(|element| { element.as_widget().operate( &mut tree.children[0], layout, + renderer, operation, ); }); diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index 5e1b5dff1c..945c935aef 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -3,8 +3,8 @@ use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::overlay; use iced_native::renderer; -use iced_native::widget::horizontal_space; use iced_native::widget::tree::{self, Tree}; +use iced_native::widget::{self, horizontal_space}; use iced_native::{ Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; @@ -142,6 +142,29 @@ where layout::Node::new(limits.max()) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + let state = tree.state.downcast_mut::(); + let mut content = self.content.borrow_mut(); + + content.resolve( + &mut state.tree.borrow_mut(), + renderer, + layout, + &self.view, + |tree, renderer, layout, element| { + element + .as_widget() + .operate(tree, layout, renderer, operation); + }, + ); + } + fn on_event( &mut self, tree: &mut Tree, diff --git a/native/src/element.rs b/native/src/element.rs index 2f1adeff59..2409b1c976 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -290,6 +290,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation, ) { struct MapOperation<'a, B> { @@ -334,8 +335,12 @@ where } } - self.widget - .operate(tree, layout, &mut MapOperation { operation }); + self.widget.operate( + tree, + layout, + renderer, + &mut MapOperation { operation }, + ); } fn on_event( @@ -473,9 +478,12 @@ where &self, state: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation, ) { - self.element.widget.operate(state, layout, operation) + self.element + .widget + .operate(state, layout, renderer, operation) } fn on_event( diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index 6d5f65630d..33a452d075 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -51,7 +51,7 @@ impl Limits { } Length::Units(units) => { let new_width = - (units as f32).min(self.max.width).max(self.min.width); + (units as f32).clamp(self.min.width, self.max.width); self.min.width = new_width; self.max.width = new_width; @@ -73,7 +73,7 @@ impl Limits { } Length::Units(units) => { let new_height = - (units as f32).min(self.max.height).max(self.min.height); + (units as f32).clamp(self.min.height, self.max.height); self.min.height = new_height; self.max.height = new_height; @@ -86,16 +86,14 @@ impl Limits { /// Applies a minimum width constraint to the current [`Limits`]. pub fn min_width(mut self, min_width: u32) -> Limits { - self.min.width = - self.min.width.max(min_width as f32).min(self.max.width); + self.min.width = self.min.width.clamp(min_width as f32, self.max.width); self } /// Applies a maximum width constraint to the current [`Limits`]. pub fn max_width(mut self, max_width: u32) -> Limits { - self.max.width = - self.max.width.min(max_width as f32).max(self.min.width); + self.max.width = self.max.width.clamp(self.min.width, max_width as f32); self } @@ -103,7 +101,7 @@ impl Limits { /// Applies a minimum height constraint to the current [`Limits`]. pub fn min_height(mut self, min_height: u32) -> Limits { self.min.height = - self.min.height.max(min_height as f32).min(self.max.height); + self.min.height.clamp(min_height as f32, self.max.height); self } @@ -111,7 +109,7 @@ impl Limits { /// Applies a maximum height constraint to the current [`Limits`]. pub fn max_height(mut self, max_height: u32) -> Limits { self.max.height = - self.max.height.min(max_height as f32).max(self.min.height); + self.max.height.clamp(self.min.height, max_height as f32); self } @@ -157,14 +155,10 @@ impl Limits { /// intrinsic size of some content. pub fn resolve(&self, intrinsic_size: Size) -> Size { Size::new( - intrinsic_size - .width - .min(self.max.width) - .max(self.fill.width), + intrinsic_size.width.clamp(self.fill.width, self.max.width), intrinsic_size .height - .min(self.max.height) - .max(self.fill.height), + .clamp(self.fill.height, self.max.height), ) } } diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 0b05b058e1..22f8b6ec80 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -46,6 +46,7 @@ where fn operate( &mut self, _layout: Layout<'_>, + _renderer: &Renderer, _operation: &mut dyn widget::Operation, ) { } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 4f5ef32ac7..498e9ae361 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -108,9 +108,10 @@ where pub fn operate( &mut self, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation, ) { - self.overlay.operate(layout, operation); + self.overlay.operate(layout, renderer, operation); } } @@ -144,6 +145,7 @@ where fn operate( &mut self, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation, ) { struct MapOperation<'a, B> { @@ -189,7 +191,7 @@ where } self.content - .operate(layout, &mut MapOperation { operation }); + .operate(layout, renderer, &mut MapOperation { operation }); } fn on_event( diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 376ce568f8..2b43829d91 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -493,6 +493,7 @@ where self.root.as_widget().operate( &mut self.state, Layout::new(&self.base), + renderer, operation, ); @@ -507,6 +508,7 @@ where overlay.operate( Layout::new(self.overlay.as_ref().unwrap()), + renderer, operation, ); } diff --git a/native/src/widget.rs b/native/src/widget.rs index a4b46ed431..f714e28a0e 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -33,6 +33,7 @@ pub mod text_input; pub mod toggler; pub mod tooltip; pub mod tree; +pub mod vertical_slider; mod action; mod id; @@ -79,6 +80,8 @@ pub use toggler::Toggler; pub use tooltip::Tooltip; #[doc(no_inline)] pub use tree::Tree; +#[doc(no_inline)] +pub use vertical_slider::VerticalSlider; pub use action::Action; pub use id::Id; @@ -172,6 +175,7 @@ where &self, _state: &mut Tree, _layout: Layout<'_>, + _renderer: &Renderer, _operation: &mut dyn Operation, ) { } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index bbd9451ce3..b4276317f6 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, ) { operation.container(None, &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), + renderer, operation, ); }); diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 8030778bff..f2ef132a8d 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, ) { 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); }) }); } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 16d0cb61bd..cdf1c85925 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, ) { operation.container(None, &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), + renderer, operation, ); }); diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 0bde288fca..8cc1ae8205 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -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, + value: T, + on_change: impl Fn(T) -> Message + 'a, +) -> widget::VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + From + 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 diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 9c83287ea1..fdbd321688 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 5de95c651f..f8dbab7461 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -294,6 +294,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation, ) { 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); }) }); } @@ -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) } }; diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 5f269d1f60..c9b0df078f 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, ) { 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, ); } diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 28e4670f4f..ea0969aa97 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, ) { 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, ) } diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index f059026f44..7d5d5be555 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, 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, diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index c689ac13f2..108e98e4a5 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, ) { 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); }) }); } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index a5e0e0e30c..20780f899a 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, ) { let state = tree.state.downcast_mut::(); @@ -174,6 +175,7 @@ where self.content.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), + renderer, operation, ); }); @@ -881,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), ); } @@ -905,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/text_input.rs b/native/src/widget/text_input.rs index 9391d1dd8f..05b47ff912 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, ) { let state = tree.state.downcast_mut::(); diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 37cafd92f6..1ae65ba6fe 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -265,8 +265,8 @@ where theme.active(&self.style, self.is_active) }; - 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, diff --git a/native/src/widget/vertical_slider.rs b/native/src/widget/vertical_slider.rs new file mode 100644 index 0000000000..28e8405ccc --- /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, + step: T, + value: T, + on_change: Box Message + 'a>, + on_release: Option, + width: u16, + height: Length, + style: ::Style, +} + +impl<'a, T, Message, Renderer> VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + From + 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(range: RangeInclusive, 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<::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 + for VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + Into + num_traits::FromPrimitive, + Message: Clone, + Renderer: crate::Renderer, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + 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::(), + &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::(), + 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::(), + ) + } +} + +impl<'a, T, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + T: 'a + Copy + Into + 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( + event: Event, + layout: Layout<'_>, + cursor_position: Point, + shell: &mut Shell<'_, Message>, + state: &mut State, + value: &mut T, + range: &RangeInclusive, + step: T, + on_change: &dyn Fn(T) -> Message, + on_release: &Option, +) -> event::Status +where + T: Copy + Into + 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( + renderer: &mut R, + layout: Layout<'_>, + cursor_position: Point, + state: &State, + value: T, + range: &RangeInclusive, + style_sheet: &dyn StyleSheet