diff options
Diffstat (limited to 'widget/src')
-rw-r--r-- | widget/src/button.rs | 13 | ||||
-rw-r--r-- | widget/src/canvas/program.rs | 6 | ||||
-rw-r--r-- | widget/src/lazy.rs | 48 | ||||
-rw-r--r-- | widget/src/lazy/component.rs | 51 | ||||
-rw-r--r-- | widget/src/lazy/responsive.rs | 36 | ||||
-rw-r--r-- | widget/src/overlay/menu.rs | 40 | ||||
-rw-r--r-- | widget/src/pick_list.rs | 44 | ||||
-rw-r--r-- | widget/src/qr_code.rs | 3 | ||||
-rw-r--r-- | widget/src/scrollable.rs | 162 | ||||
-rw-r--r-- | widget/src/toggler.rs | 4 |
10 files changed, 241 insertions, 166 deletions
diff --git a/widget/src/button.rs b/widget/src/button.rs index 0f3cbde2..8ebc9657 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -102,8 +102,17 @@ where /// Sets the message that will be produced when the [`Button`] is pressed. /// /// Unless `on_press` is called, the [`Button`] will be disabled. - pub fn on_press(mut self, msg: Message) -> Self { - self.on_press = Some(msg); + pub fn on_press(mut self, on_press: Message) -> Self { + self.on_press = Some(on_press); + self + } + + /// Sets the message that will be produced when the [`Button`] is pressed, + /// if `Some`. + /// + /// If `None`, the [`Button`] will be disabled. + pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self { + self.on_press = on_press; self } diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index 929ee285..b3f6175e 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -1,7 +1,7 @@ use crate::canvas::event::{self, Event}; use crate::canvas::mouse; use crate::core::Rectangle; -use crate::graphics::geometry::{self, Geometry}; +use crate::graphics::geometry; /// The state and logic of a [`Canvas`]. /// @@ -51,7 +51,7 @@ where theme: &Renderer::Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Vec<Geometry>; + ) -> Vec<Renderer::Geometry>; /// Returns the current mouse interaction of the [`Program`]. /// @@ -93,7 +93,7 @@ where theme: &Renderer::Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Vec<Geometry> { + ) -> Vec<Renderer::Geometry> { T::draw(self, state, renderer, theme, bounds, cursor) } diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 92a611c3..da287f06 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -20,6 +20,7 @@ use crate::core::Element; use crate::core::{ self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, }; +use crate::runtime::overlay::Nested; use ouroboros::self_referencing; use std::cell::RefCell; @@ -260,14 +261,17 @@ where .unwrap(), tree: &mut tree.children[0], overlay_builder: |element, tree| { - element.as_widget_mut().overlay(tree, layout, renderer) + element + .as_widget_mut() + .overlay(tree, layout, renderer) + .map(|overlay| RefCell::new(Nested::new(overlay))) }, } .build(), )); - let has_overlay = overlay - .with_overlay_maybe(|overlay| overlay::Element::position(overlay)); + let has_overlay = + overlay.with_overlay_maybe(|overlay| overlay.position()); has_overlay .map(|position| overlay::Element::new(position, Box::new(overlay))) @@ -275,18 +279,14 @@ where } #[self_referencing] -struct Inner<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a, -{ +struct Inner<'a, Message: 'a, Renderer: 'a> { cell: Rc<RefCell<Option<Element<'static, Message, Renderer>>>>, element: Element<'static, Message, Renderer>, tree: &'a mut Tree, #[borrows(mut element, mut tree)] - #[covariant] - overlay: Option<overlay::Element<'this, Message, Renderer>>, + #[not_covariant] + overlay: Option<RefCell<Nested<'this, Message, Renderer>>>, } struct Overlay<'a, Message, Renderer>(Option<Inner<'a, Message, Renderer>>); @@ -301,19 +301,20 @@ impl<'a, Message, Renderer> Drop for Overlay<'a, Message, Renderer> { impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> { fn with_overlay_maybe<T>( &self, - f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T, ) -> Option<T> { - self.0.as_ref().unwrap().borrow_overlay().as_ref().map(f) + self.0.as_ref().unwrap().with_overlay(|overlay| { + overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut())) + }) } fn with_overlay_mut_maybe<T>( &mut self, - f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T, ) -> Option<T> { - self.0 - .as_mut() - .unwrap() - .with_overlay_mut(|overlay| overlay.as_mut().map(f)) + self.0.as_mut().unwrap().with_overlay_mut(|overlay| { + overlay.as_mut().map(|nested| (f)(nested.get_mut())) + }) } } @@ -329,9 +330,7 @@ where position: Point, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - let translation = position - overlay.position(); - - overlay.layout(renderer, bounds, translation) + overlay.layout(renderer, bounds, position) }) .unwrap_or_default() } @@ -377,9 +376,14 @@ where .unwrap_or(event::Status::Ignored) } - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + fn is_over( + &self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { self.with_overlay_maybe(|overlay| { - overlay.is_over(layout, cursor_position) + overlay.is_over(layout, renderer, cursor_position) }) .unwrap_or_default() } diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index f462c8cf..f955d9dd 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -9,6 +9,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::{ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; +use crate::runtime::overlay::Nested; use ouroboros::self_referencing; use std::cell::RefCell; @@ -455,11 +456,18 @@ where overlay_builder: |instance, tree| { instance.state.get_mut().as_mut().unwrap().with_element_mut( move |element| { - element.as_mut().unwrap().as_widget_mut().overlay( - &mut tree.children[0], - layout, - renderer, - ) + element + .as_mut() + .unwrap() + .as_widget_mut() + .overlay( + &mut tree.children[0], + layout, + renderer, + ) + .map(|overlay| { + RefCell::new(Nested::new(overlay)) + }) }, ) }, @@ -468,7 +476,7 @@ where )); let has_overlay = overlay.0.as_ref().unwrap().with_overlay(|overlay| { - overlay.as_ref().map(overlay::Element::position) + overlay.as_ref().map(|nested| nested.borrow().position()) }); has_overlay.map(|position| { @@ -503,8 +511,8 @@ struct Inner<'a, 'b, Message, Renderer, Event, S> { types: PhantomData<(Message, Event, S)>, #[borrows(mut instance, mut tree)] - #[covariant] - overlay: Option<overlay::Element<'this, Event, Renderer>>, + #[not_covariant] + overlay: Option<RefCell<Nested<'this, Event, Renderer>>>, } struct OverlayInstance<'a, 'b, Message, Renderer, Event, S> { @@ -516,7 +524,7 @@ impl<'a, 'b, Message, Renderer, Event, S> { fn with_overlay_maybe<T>( &self, - f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Event, Renderer>) -> T, ) -> Option<T> { self.overlay .as_ref() @@ -524,14 +532,14 @@ impl<'a, 'b, Message, Renderer, Event, S> .0 .as_ref() .unwrap() - .borrow_overlay() - .as_ref() - .map(f) + .with_overlay(|overlay| { + overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut())) + }) } fn with_overlay_mut_maybe<T>( &mut self, - f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Event, Renderer>) -> T, ) -> Option<T> { self.overlay .as_mut() @@ -539,7 +547,9 @@ impl<'a, 'b, Message, Renderer, Event, S> .0 .as_mut() .unwrap() - .with_overlay_mut(|overlay| overlay.as_mut().map(f)) + .with_overlay_mut(|overlay| { + overlay.as_mut().map(|nested| (f)(nested.get_mut())) + }) } } @@ -556,9 +566,7 @@ where position: Point, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - let translation = position - overlay.position(); - - overlay.layout(renderer, bounds, translation) + overlay.layout(renderer, bounds, position) }) .unwrap_or_default() } @@ -655,9 +663,14 @@ where event_status } - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + fn is_over( + &self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { self.with_overlay_maybe(|overlay| { - overlay.is_over(layout, cursor_position) + overlay.is_over(layout, renderer, cursor_position) }) .unwrap_or_default() } diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index bd6385bc..07300857 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -9,6 +9,7 @@ use crate::core::{ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; use crate::horizontal_space; +use crate::runtime::overlay::Nested; use ouroboros::self_referencing; use std::cell::{RefCell, RefMut}; @@ -298,13 +299,13 @@ where element .as_widget_mut() .overlay(tree, content_layout, renderer) + .map(|overlay| RefCell::new(Nested::new(overlay))) }, } .build(); - let has_overlay = overlay.with_overlay(|overlay| { - overlay.as_ref().map(overlay::Element::position) - }); + let has_overlay = + overlay.with_overlay_maybe(|overlay| overlay.position()); has_overlay .map(|position| overlay::Element::new(position, Box::new(overlay))) @@ -329,23 +330,27 @@ struct Overlay<'a, 'b, Message, Renderer> { types: PhantomData<Message>, #[borrows(mut content, mut tree)] - #[covariant] - overlay: Option<overlay::Element<'this, Message, Renderer>>, + #[not_covariant] + overlay: Option<RefCell<Nested<'this, Message, Renderer>>>, } impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> { fn with_overlay_maybe<T>( &self, - f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T, ) -> Option<T> { - self.borrow_overlay().as_ref().map(f) + self.with_overlay(|overlay| { + overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut())) + }) } fn with_overlay_mut_maybe<T>( &mut self, - f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T, ) -> Option<T> { - self.with_overlay_mut(|overlay| overlay.as_mut().map(f)) + self.with_overlay_mut(|overlay| { + overlay.as_mut().map(|nested| (f)(nested.get_mut())) + }) } } @@ -361,9 +366,7 @@ where position: Point, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - let translation = position - overlay.position(); - - overlay.layout(renderer, bounds, translation) + overlay.layout(renderer, bounds, position) }) .unwrap_or_default() } @@ -409,9 +412,14 @@ where .unwrap_or(event::Status::Ignored) } - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + fn is_over( + &self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { self.with_overlay_maybe(|overlay| { - overlay.is_over(layout, cursor_position) + overlay.is_over(layout, renderer, cursor_position) }) .unwrap_or_default() } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b699def0..ccf4dfb5 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -19,7 +19,7 @@ pub use iced_style::menu::{Appearance, StyleSheet}; /// A list of selectable options. #[allow(missing_debug_implementations)] -pub struct Menu<'a, T, Renderer = crate::Renderer> +pub struct Menu<'a, T, Message, Renderer = crate::Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -27,7 +27,7 @@ where state: &'a mut State, options: &'a [T], hovered_option: &'a mut Option<usize>, - last_selection: &'a mut Option<T>, + on_selected: Box<dyn FnMut(T) -> Message + 'a>, width: f32, padding: Padding, text_size: Option<f32>, @@ -37,9 +37,10 @@ where style: <Renderer::Theme as StyleSheet>::Style, } -impl<'a, T, Renderer> Menu<'a, T, Renderer> +impl<'a, T, Message, Renderer> Menu<'a, T, Message, Renderer> where T: ToString + Clone, + Message: 'a, Renderer: text::Renderer + 'a, Renderer::Theme: StyleSheet + container::StyleSheet + scrollable::StyleSheet, @@ -50,13 +51,13 @@ where state: &'a mut State, options: &'a [T], hovered_option: &'a mut Option<usize>, - last_selection: &'a mut Option<T>, + on_selected: impl FnMut(T) -> Message + 'a, ) -> Self { Menu { state, options, hovered_option, - last_selection, + on_selected: Box::new(on_selected), width: 0.0, padding: Padding::ZERO, text_size: None, @@ -121,7 +122,7 @@ where /// The `target_height` will be used to display the menu either on top /// of the target or under it, depending on the screen position and the /// dimensions of the [`Menu`]. - pub fn overlay<Message: 'a>( + pub fn overlay( self, position: Point, target_height: f32, @@ -174,7 +175,10 @@ where Renderer::Theme: StyleSheet + container::StyleSheet + scrollable::StyleSheet, { - pub fn new<T>(menu: Menu<'a, T, Renderer>, target_height: f32) -> Self + pub fn new<T>( + menu: Menu<'a, T, Message, Renderer>, + target_height: f32, + ) -> Self where T: Clone + ToString, { @@ -182,7 +186,7 @@ where state, options, hovered_option, - last_selection, + on_selected, width, padding, font, @@ -195,7 +199,7 @@ where let container = Container::new(Scrollable::new(List { options, hovered_option, - last_selection, + on_selected, font, text_size, text_line_height, @@ -306,14 +310,14 @@ where } } -struct List<'a, T, Renderer> +struct List<'a, T, Message, Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { options: &'a [T], hovered_option: &'a mut Option<usize>, - last_selection: &'a mut Option<T>, + on_selected: Box<dyn FnMut(T) -> Message + 'a>, padding: Padding, text_size: Option<f32>, text_line_height: text::LineHeight, @@ -323,7 +327,7 @@ where } impl<'a, T, Message, Renderer> Widget<Message, Renderer> - for List<'a, T, Renderer> + for List<'a, T, Message, Renderer> where T: Clone + ToString, Renderer: text::Renderer, @@ -372,14 +376,15 @@ where cursor: mouse::Cursor, renderer: &Renderer, _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if cursor.is_over(layout.bounds()) { if let Some(index) = *self.hovered_option { if let Some(option) = self.options.get(index) { - *self.last_selection = Some(option.clone()); + shell.publish((self.on_selected)(option.clone())); + return event::Status::Captured; } } } @@ -417,7 +422,8 @@ where if let Some(index) = *self.hovered_option { if let Some(option) = self.options.get(index) { - *self.last_selection = Some(option.clone()); + shell.publish((self.on_selected)(option.clone())); + return event::Status::Captured; } } } @@ -521,7 +527,7 @@ where } } -impl<'a, T, Message, Renderer> From<List<'a, T, Renderer>> +impl<'a, T, Message, Renderer> From<List<'a, T, Message, Renderer>> for Element<'a, Message, Renderer> where T: ToString + Clone, @@ -529,7 +535,7 @@ where Renderer: 'a + text::Renderer, Renderer::Theme: StyleSheet, { - fn from(list: List<'a, T, Renderer>) -> Self { + fn from(list: List<'a, T, Message, Renderer>) -> Self { Element::new(list) } } diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index dc53a9c8..832aae6b 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -157,11 +157,11 @@ where From<<Renderer::Theme as StyleSheet>::Style>, { fn tag(&self) -> tree::Tag { - tree::Tag::of::<State<T>>() + tree::Tag::of::<State>() } fn state(&self) -> tree::State { - tree::State::new(State::<T>::new()) + tree::State::new(State::new()) } fn width(&self) -> Length { @@ -209,7 +209,7 @@ where self.on_selected.as_ref(), self.selected.as_ref(), &self.options, - || tree.state.downcast_mut::<State<T>>(), + || tree.state.downcast_mut::<State>(), ) } @@ -249,7 +249,7 @@ where self.selected.as_ref(), &self.handle, &self.style, - || tree.state.downcast_ref::<State<T>>(), + || tree.state.downcast_ref::<State>(), ) } @@ -259,7 +259,7 @@ where layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - let state = tree.state.downcast_mut::<State<T>>(); + let state = tree.state.downcast_mut::<State>(); overlay( layout, @@ -269,6 +269,7 @@ where self.text_shaping, self.font.unwrap_or_else(|| renderer.default_font()), &self.options, + &self.on_selected, self.style.clone(), ) } @@ -295,15 +296,14 @@ where /// The local state of a [`PickList`]. #[derive(Debug)] -pub struct State<T> { +pub struct State { menu: menu::State, keyboard_modifiers: keyboard::Modifiers, is_open: bool, hovered_option: Option<usize>, - last_selection: Option<T>, } -impl<T> State<T> { +impl State { /// Creates a new [`State`] for a [`PickList`]. pub fn new() -> Self { Self { @@ -311,12 +311,11 @@ impl<T> State<T> { keyboard_modifiers: keyboard::Modifiers::default(), is_open: bool::default(), hovered_option: Option::default(), - last_selection: Option::default(), } } } -impl<T> Default for State<T> { +impl Default for State { fn default() -> Self { Self::new() } @@ -436,7 +435,7 @@ pub fn update<'a, T, Message>( on_selected: &dyn Fn(T) -> Message, selected: Option<&T>, options: &[T], - state: impl FnOnce() -> &'a mut State<T>, + state: impl FnOnce() -> &'a mut State, ) -> event::Status where T: PartialEq + Clone + 'a, @@ -446,7 +445,7 @@ where | Event::Touch(touch::Event::FingerPressed { .. }) => { let state = state(); - let event_status = if state.is_open { + if state.is_open { // Event wasn't processed by overlay, so cursor was clicked either outside it's // bounds or on the drop-down, either way we close the overlay. state.is_open = false; @@ -460,16 +459,6 @@ where event::Status::Captured } else { event::Status::Ignored - }; - - if let Some(last_selection) = state.last_selection.take() { - shell.publish((on_selected)(last_selection)); - - state.is_open = false; - - event::Status::Captured - } else { - event_status } } Event::Mouse(mouse::Event::WheelScrolled { @@ -544,12 +533,13 @@ pub fn mouse_interaction( /// Returns the current overlay of a [`PickList`]. pub fn overlay<'a, T, Message, Renderer>( layout: Layout<'_>, - state: &'a mut State<T>, + state: &'a mut State, padding: Padding, text_size: Option<f32>, text_shaping: text::Shaping, font: Renderer::Font, options: &'a [T], + on_selected: &'a dyn Fn(T) -> Message, style: <Renderer::Theme as StyleSheet>::Style, ) -> Option<overlay::Element<'a, Message, Renderer>> where @@ -570,7 +560,11 @@ where &mut state.menu, options, &mut state.hovered_option, - &mut state.last_selection, + |option| { + state.is_open = false; + + (on_selected)(option) + }, ) .width(bounds.width) .padding(padding) @@ -603,7 +597,7 @@ pub fn draw<'a, T, Renderer>( selected: Option<&T>, handle: &Handle<Renderer::Font>, style: &<Renderer::Theme as StyleSheet>::Style, - state: impl FnOnce() -> &'a State<T>, + state: impl FnOnce() -> &'a State, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 06be93c0..51a541fd 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -7,6 +7,7 @@ use crate::core::widget::Tree; use crate::core::{ Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; +use crate::graphics::geometry::Renderer as _; use crate::Renderer; use thiserror::Error; @@ -121,7 +122,7 @@ impl<'a, Message, Theme> Widget<Message, Renderer<Theme>> for QRCode<'a> { let translation = Vector::new(bounds.x, bounds.y); renderer.with_translation(translation, |renderer| { - renderer.draw_primitive(geometry.0); + renderer.draw(vec![geometry]); }); } } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 010befac..473124ca 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -29,8 +29,7 @@ where id: Option<Id>, width: Length, height: Length, - vertical: Properties, - horizontal: Option<Properties>, + direction: Direction, content: Element<'a, Message, Renderer>, on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>, style: <Renderer::Theme as StyleSheet>::Style, @@ -47,8 +46,7 @@ where id: None, width: Length::Shrink, height: Length::Shrink, - vertical: Properties::default(), - horizontal: None, + direction: Default::default(), content: content.into(), on_scroll: None, style: Default::default(), @@ -73,15 +71,9 @@ where self } - /// Configures the vertical scrollbar of the [`Scrollable`] . - pub fn vertical_scroll(mut self, properties: Properties) -> Self { - self.vertical = properties; - self - } - - /// Configures the horizontal scrollbar of the [`Scrollable`] . - pub fn horizontal_scroll(mut self, properties: Properties) -> Self { - self.horizontal = Some(properties); + /// Sets the [`Direction`] of the [`Scrollable`] . + pub fn direction(mut self, direction: Direction) -> Self { + self.direction = direction; self } @@ -103,8 +95,50 @@ where } } +/// The direction of [`Scrollable`]. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Direction { + /// Vertical scrolling + Vertical(Properties), + /// Horizontal scrolling + Horizontal(Properties), + /// Both vertical and horizontal scrolling + Both { + /// The properties of the vertical scrollbar. + vertical: Properties, + /// The properties of the horizontal scrollbar. + horizontal: Properties, + }, +} + +impl Direction { + /// Returns the [`Properties`] of the horizontal scrollbar, if any. + pub fn horizontal(&self) -> Option<&Properties> { + match self { + Self::Horizontal(properties) => Some(properties), + Self::Both { horizontal, .. } => Some(horizontal), + _ => None, + } + } + + /// Returns the [`Properties`] of the vertical scrollbar, if any. + pub fn vertical(&self) -> Option<&Properties> { + match self { + Self::Vertical(properties) => Some(properties), + Self::Both { vertical, .. } => Some(vertical), + _ => None, + } + } +} + +impl Default for Direction { + fn default() -> Self { + Self::Vertical(Properties::default()) + } +} + /// Properties of a scrollbar within a [`Scrollable`]. -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Properties { width: f32, margin: f32, @@ -186,7 +220,7 @@ where limits, self.width, self.height, - self.horizontal.is_some(), + &self.direction, |renderer, limits| { self.content.as_widget().layout(renderer, limits) }, @@ -234,8 +268,7 @@ where cursor, clipboard, shell, - &self.vertical, - self.horizontal.as_ref(), + &self.direction, &self.on_scroll, |event, layout, cursor, clipboard, shell| { self.content.as_widget_mut().on_event( @@ -267,8 +300,7 @@ where theme, layout, cursor, - &self.vertical, - self.horizontal.as_ref(), + &self.direction, &self.style, |renderer, layout, cursor, viewport| { self.content.as_widget().draw( @@ -296,8 +328,7 @@ where tree.state.downcast_ref::<State>(), layout, cursor, - &self.vertical, - self.horizontal.as_ref(), + &self.direction, |layout, cursor, viewport| { self.content.as_widget().mouse_interaction( &tree.children[0], @@ -327,10 +358,11 @@ where let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); - let offset = tree - .state - .downcast_ref::<State>() - .offset(bounds, content_bounds); + let offset = tree.state.downcast_ref::<State>().offset( + &self.direction, + bounds, + content_bounds, + ); overlay.translate(Vector::new(-offset.x, -offset.y)) }) @@ -399,20 +431,24 @@ pub fn layout<Renderer>( limits: &layout::Limits, width: Length, height: Length, - horizontal_enabled: bool, + 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, 0.0), + Size::new(limits.min().width, limits.min().height), Size::new( - if horizontal_enabled { + if direction.horizontal().is_some() { f32::INFINITY } else { limits.max().width }, - f32::MAX, + if direction.vertical().is_some() { + f32::MAX + } else { + limits.max().height + }, ), ); @@ -431,8 +467,7 @@ pub fn update<Message>( cursor: mouse::Cursor, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - vertical: &Properties, - horizontal: Option<&Properties>, + direction: &Direction, on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>, update_content: impl FnOnce( Event, @@ -448,8 +483,7 @@ pub fn update<Message>( let content = layout.children().next().unwrap(); let content_bounds = content.bounds(); - let scrollbars = - Scrollbars::new(state, vertical, horizontal, bounds, content_bounds); + let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds); let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = scrollbars.is_mouse_over(cursor); @@ -460,7 +494,8 @@ pub fn update<Message>( if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => { mouse::Cursor::Available( - cursor_position + state.offset(bounds, content_bounds), + cursor_position + + state.offset(direction, bounds, content_bounds), ) } _ => mouse::Cursor::Unavailable, @@ -713,8 +748,7 @@ pub fn mouse_interaction( state: &State, layout: Layout<'_>, cursor: mouse::Cursor, - vertical: &Properties, - horizontal: Option<&Properties>, + direction: &Direction, content_interaction: impl FnOnce( Layout<'_>, mouse::Cursor, @@ -727,8 +761,7 @@ pub fn mouse_interaction( let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); - let scrollbars = - Scrollbars::new(state, vertical, horizontal, bounds, content_bounds); + let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds); let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = scrollbars.is_mouse_over(cursor); @@ -738,7 +771,7 @@ pub fn mouse_interaction( { mouse::Interaction::Idle } else { - let offset = state.offset(bounds, content_bounds); + let offset = state.offset(direction, bounds, content_bounds); let cursor = match cursor_over_scrollable { Some(cursor_position) @@ -768,8 +801,7 @@ pub fn draw<Renderer>( theme: &Renderer::Theme, layout: Layout<'_>, cursor: mouse::Cursor, - vertical: &Properties, - horizontal: Option<&Properties>, + direction: &Direction, style: &<Renderer::Theme as StyleSheet>::Style, draw_content: impl FnOnce(&mut Renderer, Layout<'_>, mouse::Cursor, &Rectangle), ) where @@ -780,14 +812,13 @@ pub fn draw<Renderer>( let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); - let scrollbars = - Scrollbars::new(state, vertical, horizontal, bounds, content_bounds); + let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds); let cursor_over_scrollable = cursor.position_over(bounds); let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = scrollbars.is_mouse_over(cursor); - let offset = state.offset(bounds, content_bounds); + let offset = state.offset(direction, bounds, content_bounds); let cursor = match cursor_over_scrollable { Some(cursor_position) @@ -1126,16 +1157,25 @@ impl State { ); } - /// Returns the scrolling offset of the [`State`], given the bounds of the - /// [`Scrollable`] and its contents. + /// Returns the scrolling offset of the [`State`], given a [`Direction`], + /// the bounds of the [`Scrollable`] and its contents. pub fn offset( &self, + direction: &Direction, bounds: Rectangle, content_bounds: Rectangle, ) -> Vector { Vector::new( - self.offset_x.absolute(bounds.width, content_bounds.width), - self.offset_y.absolute(bounds.height, content_bounds.height), + if direction.horizontal().is_some() { + self.offset_x.absolute(bounds.width, content_bounds.width) + } else { + 0.0 + }, + if direction.vertical().is_some() { + self.offset_y.absolute(bounds.height, content_bounds.height) + } else { + 0.0 + }, ) } @@ -1157,22 +1197,21 @@ impl Scrollbars { /// Create y and/or x scrollbar(s) if content is overflowing the [`Scrollable`] bounds. fn new( state: &State, - vertical: &Properties, - horizontal: Option<&Properties>, + direction: &Direction, bounds: Rectangle, content_bounds: Rectangle, ) -> Self { - let offset = state.offset(bounds, content_bounds); + let offset = state.offset(direction, bounds, content_bounds); - let show_scrollbar_x = horizontal.and_then(|h| { - if content_bounds.width > bounds.width { - Some(h) - } else { - None - } - }); + let show_scrollbar_x = direction + .horizontal() + .filter(|_| content_bounds.width > bounds.width); + + let show_scrollbar_y = direction + .vertical() + .filter(|_| content_bounds.height > bounds.height); - let y_scrollbar = if content_bounds.height > bounds.height { + let y_scrollbar = if let Some(vertical) = show_scrollbar_y { let Properties { width, margin, @@ -1240,9 +1279,8 @@ impl Scrollbars { // Need to adjust the width of the horizontal scrollbar if the vertical scrollbar // is present - let scrollbar_y_width = y_scrollbar.map_or(0.0, |_| { - vertical.width.max(vertical.scroller_width) + vertical.margin - }); + let scrollbar_y_width = show_scrollbar_y + .map_or(0.0, |v| v.width.max(v.scroller_width) + v.margin); let total_scrollbar_height = width.max(scroller_width) + 2.0 * margin; diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 8b51f2d0..1b31765f 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -5,6 +5,7 @@ use crate::core::layout; use crate::core::mouse; use crate::core::renderer; use crate::core::text; +use crate::core::touch; use crate::core::widget::Tree; use crate::core::{ Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Rectangle, @@ -208,7 +209,8 @@ where shell: &mut Shell<'_, Message>, ) -> event::Status { match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { let mouse_over = cursor.is_over(layout.bounds()); if mouse_over { |