From 019af8ddbf96680ffcee2b3407819e90575760cb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 16 Feb 2022 17:07:25 +0700 Subject: Add `overlay` support in `iced_pure` and port `PickList` :tada: --- pure/src/lib.rs | 13 +++ pure/src/overlay.rs | 21 ++++ pure/src/widget.rs | 12 +++ pure/src/widget/button.rs | 14 +++ pure/src/widget/column.rs | 10 ++ pure/src/widget/container.rs | 14 +++ pure/src/widget/pick_list.rs | 245 ++++++++++++++++++++++++++++++++++++++++++ pure/src/widget/row.rs | 10 ++ pure/src/widget/scrollable.rs | 14 +++ 9 files changed, 353 insertions(+) create mode 100644 pure/src/overlay.rs create mode 100644 pure/src/widget/pick_list.rs (limited to 'pure') diff --git a/pure/src/lib.rs b/pure/src/lib.rs index 07f068cc..bab3bbc7 100644 --- a/pure/src/lib.rs +++ b/pure/src/lib.rs @@ -1,3 +1,4 @@ +pub mod overlay; pub mod widget; pub(crate) mod flex; @@ -129,6 +130,18 @@ where renderer, ) } + + fn overlay( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.element.as_widget_mut().overlay( + &mut self.state.state_tree, + layout, + renderer, + ) + } } impl<'a, Message, Renderer> Into> diff --git a/pure/src/overlay.rs b/pure/src/overlay.rs new file mode 100644 index 00000000..b009fde8 --- /dev/null +++ b/pure/src/overlay.rs @@ -0,0 +1,21 @@ +use crate::Tree; + +use iced_native::Layout; + +pub use iced_native::overlay::*; + +pub fn from_children<'a, Message, Renderer>( + children: &'a mut [crate::Element<'_, Message, Renderer>], + tree: &'a mut Tree, + layout: Layout<'_>, + renderer: &Renderer, +) -> Option> { + children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .filter_map(|((child, state), layout)| { + child.as_widget_mut().overlay(state, layout, renderer) + }) + .next() +} diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 03b668d3..6dda653d 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -5,6 +5,7 @@ mod checkbox; mod column; mod container; mod element; +mod pick_list; mod radio; mod row; mod scrollable; @@ -21,6 +22,7 @@ pub use column::Column; pub use container::Container; pub use element::Element; pub use image::Image; +pub use pick_list::PickList; pub use radio::Radio; pub use row::Row; pub use scrollable::Scrollable; @@ -34,6 +36,7 @@ pub use tree::Tree; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; use iced_native::mouse; +use iced_native::overlay; use iced_native::renderer; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; @@ -97,6 +100,15 @@ pub trait Widget { ) -> event::Status { event::Status::Ignored } + + fn overlay<'a>( + &'a mut self, + _state: &'a mut Tree, + _layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option> { + None + } } pub fn container<'a, Message, Renderer>( diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index 55cbf8b4..f5e78933 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -1,3 +1,4 @@ +use crate::overlay; use crate::widget::tree::{self, Tree}; use crate::widget::{Element, Widget}; @@ -206,6 +207,19 @@ where self.on_press.is_some(), ) } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } } impl<'a, Message, Renderer> Into> diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs index 4ab3e00d..1f025335 100644 --- a/pure/src/widget/column.rs +++ b/pure/src/widget/column.rs @@ -1,4 +1,5 @@ use crate::flex; +use crate::overlay; use crate::widget::{Element, Tree, Widget}; use iced_native::event::{self, Event}; @@ -216,6 +217,15 @@ where child.as_widget().hash_layout(state); } } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + overlay::from_children(&mut self.children, tree, layout, renderer) + } } impl<'a, Message, Renderer> Into> diff --git a/pure/src/widget/container.rs b/pure/src/widget/container.rs index f42b127d..8ad6a064 100644 --- a/pure/src/widget/container.rs +++ b/pure/src/widget/container.rs @@ -5,6 +5,7 @@ use iced_native::alignment; use iced_native::event::{self, Event}; use iced_native::layout; use iced_native::mouse; +use iced_native::overlay; use iced_native::renderer; use iced_native::widget::container; use iced_native::{ @@ -237,6 +238,19 @@ where self.content.as_widget().hash_layout(state); } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } } impl<'a, Message, Renderer> From> diff --git a/pure/src/widget/pick_list.rs b/pure/src/widget/pick_list.rs new file mode 100644 index 00000000..324950e1 --- /dev/null +++ b/pure/src/widget/pick_list.rs @@ -0,0 +1,245 @@ +//! Display a dropdown list of selectable values. +use crate::widget::tree::{self, Tree}; +use crate::{Element, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::text; +use iced_native::widget::pick_list; +use iced_native::{ + Clipboard, Hasher, Layout, Length, Padding, Point, Rectangle, Shell, +}; + +use std::borrow::Cow; + +pub use iced_style::pick_list::{Style, StyleSheet}; + +/// A widget for selecting a single value from a list of options. +#[allow(missing_debug_implementations)] +pub struct PickList<'a, T, Message, Renderer: text::Renderer> +where + [T]: ToOwned>, +{ + on_selected: Box Message + 'a>, + options: Cow<'a, [T]>, + placeholder: Option, + selected: Option, + width: Length, + padding: Padding, + text_size: Option, + font: Renderer::Font, + style_sheet: Box, +} + +impl<'a, T: 'a, Message, Renderer: text::Renderer> + PickList<'a, T, Message, Renderer> +where + T: ToString + Eq, + [T]: ToOwned>, +{ + /// The default padding of a [`PickList`]. + pub const DEFAULT_PADDING: Padding = Padding::new(5); + + /// Creates a new [`PickList`] with the given [`State`], a list of options, + /// the current selected value, and the message to produce when an option is + /// selected. + pub fn new( + options: impl Into>, + selected: Option, + on_selected: impl Fn(T) -> Message + 'a, + ) -> Self { + Self { + on_selected: Box::new(on_selected), + options: options.into(), + placeholder: None, + selected, + width: Length::Shrink, + text_size: None, + padding: Self::DEFAULT_PADDING, + font: Default::default(), + style_sheet: Default::default(), + } + } + + /// Sets the placeholder of the [`PickList`]. + pub fn placeholder(mut self, placeholder: impl Into) -> Self { + self.placeholder = Some(placeholder.into()); + self + } + + /// Sets the width of the [`PickList`]. + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the [`Padding`] of the [`PickList`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the text size of the [`PickList`]. + pub fn text_size(mut self, size: u16) -> Self { + self.text_size = Some(size); + self + } + + /// Sets the font of the [`PickList`]. + pub fn font(mut self, font: Renderer::Font) -> Self { + self.font = font; + self + } + + /// Sets the style of the [`PickList`]. + pub fn style( + mut self, + style_sheet: impl Into>, + ) -> Self { + self.style_sheet = style_sheet.into(); + self + } +} + +impl<'a, T: 'a, Message, Renderer> Widget + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq + 'static, + [T]: ToOwned>, + Message: 'static, + Renderer: text::Renderer + 'a, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(pick_list::State::::new()) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + pick_list::layout( + renderer, + limits, + self.width, + self.padding, + self.text_size, + &self.font, + self.placeholder.as_ref().map(String::as_str), + &self.options, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + pick_list::hash_layout( + state, + self.width, + self.padding, + self.text_size, + self.placeholder.as_ref().map(String::as_str), + &self.options, + ) + } + + 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 { + pick_list::update( + event, + layout, + cursor_position, + shell, + self.on_selected.as_ref(), + self.selected.as_ref(), + &self.options, + || tree.state.downcast_mut::>(), + ) + } + + fn mouse_interaction( + &self, + _tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + pick_list::mouse_interaction(layout, cursor_position) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + ) { + pick_list::draw( + renderer, + layout, + cursor_position, + self.padding, + self.text_size, + &self.font, + self.placeholder.as_ref().map(String::as_str), + self.selected.as_ref(), + self.style_sheet.as_ref(), + ) + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option> { + let state = tree.state.downcast_mut::>(); + + pick_list::overlay( + layout, + state, + self.padding, + self.text_size, + self.font.clone(), + &self.options, + self.style_sheet.as_ref(), + ) + } +} + +impl<'a, T: 'a, Message, Renderer> Into> + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq + 'static, + [T]: ToOwned>, + Renderer: text::Renderer + 'a, + Message: 'static, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} diff --git a/pure/src/widget/row.rs b/pure/src/widget/row.rs index 1f281446..29128589 100644 --- a/pure/src/widget/row.rs +++ b/pure/src/widget/row.rs @@ -1,4 +1,5 @@ use crate::flex; +use crate::overlay; use crate::widget::{Element, Tree, Widget}; use iced_native::event::{self, Event}; @@ -202,6 +203,15 @@ where child.as_widget().hash_layout(state); } } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + overlay::from_children(&mut self.children, tree, layout, renderer) + } } impl<'a, Message, Renderer> Into> diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index c3289f9e..6653125e 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -1,3 +1,4 @@ +use crate::overlay; use crate::widget::tree::{self, Tree}; use crate::{Element, Widget}; @@ -230,6 +231,19 @@ where }, ) } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } } impl<'a, Message, Renderer> From> -- cgit