diff options
author | 2020-07-16 05:00:37 +0200 | |
---|---|---|
committer | 2020-07-16 05:00:37 +0200 | |
commit | da5da3958e3b7fe85e371846a795c4ae05cb2df7 (patch) | |
tree | cd9f353e005ebc9c49d987125ba168a87982c629 /native/src/widget | |
parent | 62ec03a0afe8566a8f0b06675990a83fd65de1a9 (diff) | |
parent | 31c30fedd5e5ad74cc1f66162d7e5c0e5e3cf420 (diff) | |
download | iced-da5da3958e3b7fe85e371846a795c4ae05cb2df7.tar.gz iced-da5da3958e3b7fe85e371846a795c4ae05cb2df7.tar.bz2 iced-da5da3958e3b7fe85e371846a795c4ae05cb2df7.zip |
Merge pull request #444 from hecrj/feature/overlay
Overlay support and `PickList` widget
Diffstat (limited to 'native/src/widget')
-rw-r--r-- | native/src/widget/column.rs | 15 | ||||
-rw-r--r-- | native/src/widget/container.rs | 11 | ||||
-rw-r--r-- | native/src/widget/pane_grid.rs | 15 | ||||
-rw-r--r-- | native/src/widget/pane_grid/content.rs | 19 | ||||
-rw-r--r-- | native/src/widget/pick_list.rs | 320 | ||||
-rw-r--r-- | native/src/widget/row.rs | 15 | ||||
-rw-r--r-- | native/src/widget/scrollable.rs | 28 | ||||
-rw-r--r-- | native/src/widget/space.rs | 2 |
8 files changed, 411 insertions, 14 deletions
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 259a7e6e..42cfe9b9 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -2,8 +2,8 @@ use std::hash::Hash; use crate::{ - layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Widget, + layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Widget, }; use std::u32; @@ -204,6 +204,17 @@ where child.widget.hash_layout(state); } } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option<overlay::Element<'_, Message, Renderer>> { + self.children + .iter_mut() + .zip(layout.children()) + .filter_map(|(child, layout)| child.widget.overlay(layout)) + .next() + } } /// The renderer of a [`Column`]. diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 2590fe3b..b8316e62 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -2,8 +2,8 @@ use std::hash::Hash; use crate::{ - layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Widget, + layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Rectangle, Widget, }; use std::u32; @@ -214,6 +214,13 @@ where self.content.hash_layout(state); } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option<overlay::Element<'_, Message, Renderer>> { + self.content.overlay(layout.children().next().unwrap()) + } } /// The renderer of a [`Container`]. diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index c472d043..0b237b9e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -29,8 +29,8 @@ pub use state::{Focus, State}; pub use title_bar::TitleBar; use crate::{ - container, keyboard, layout, mouse, row, text, Clipboard, Element, Event, - Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, + container, keyboard, layout, mouse, overlay, row, text, Clipboard, Element, + Event, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; /// A collection of panes distributed using either vertical or horizontal splits @@ -636,6 +636,17 @@ where element.hash_layout(state); } } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option<overlay::Element<'_, Message, Renderer>> { + self.elements + .iter_mut() + .zip(layout.children()) + .filter_map(|((_, pane), layout)| pane.overlay(layout)) + .next() + } } /// The renderer of a [`PaneGrid`]. diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 1f5ce640..39a92186 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -1,5 +1,6 @@ use crate::container; use crate::layout; +use crate::overlay; use crate::pane_grid::{self, TitleBar}; use crate::{Clipboard, Element, Event, Hasher, Layout, Point, Size}; @@ -184,6 +185,24 @@ where pub(crate) fn hash_layout(&self, state: &mut Hasher) { self.body.hash_layout(state); } + + pub(crate) fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option<overlay::Element<'_, Message, Renderer>> { + let body_layout = if self.title_bar.is_some() { + let mut children = layout.children(); + + // Overlays only allowed in the pane body, for now at least. + let _title_bar_layout = children.next(); + + children.next()? + } else { + layout + }; + + self.body.overlay(body_layout) + } } impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer> diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs new file mode 100644 index 00000000..9f62e550 --- /dev/null +++ b/native/src/widget/pick_list.rs @@ -0,0 +1,320 @@ +//! Display a dropdown list of selectable values. +use crate::{ + layout, mouse, overlay, + overlay::menu::{self, Menu}, + scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, +}; +use std::borrow::Cow; + +/// A widget for selecting a single value from a list of options. +#[allow(missing_debug_implementations)] +pub struct PickList<'a, T, Message, Renderer: self::Renderer> +where + [T]: ToOwned<Owned = Vec<T>>, +{ + menu: &'a mut menu::State, + on_selected: Box<dyn Fn(T) -> Message>, + options: Cow<'a, [T]>, + selected: Option<T>, + width: Length, + padding: u16, + text_size: Option<u16>, + font: Renderer::Font, + style: <Renderer as self::Renderer>::Style, + is_open: bool, +} + +/// The local state of a [`PickList`]. +/// +/// [`PickList`]: struct.PickList.html +#[derive(Debug, Clone, Default)] +pub struct State { + menu: menu::State, +} + +impl<'a, T: 'a, Message, Renderer: self::Renderer> + PickList<'a, T, Message, Renderer> +where + T: ToString, + [T]: ToOwned<Owned = Vec<T>>, +{ + /// 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. + /// + /// [`PickList`]: struct.PickList.html + /// [`State`]: struct.State.html + pub fn new( + state: &'a mut State, + options: impl Into<Cow<'a, [T]>>, + selected: Option<T>, + on_selected: impl Fn(T) -> Message + 'static, + ) -> Self { + let is_open = state.menu.is_open(); + + Self { + menu: &mut state.menu, + on_selected: Box::new(on_selected), + options: options.into(), + selected, + width: Length::Shrink, + text_size: None, + padding: Renderer::DEFAULT_PADDING, + font: Default::default(), + style: Default::default(), + is_open, + } + } + + /// Sets the width of the [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the padding of the [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + pub fn padding(mut self, padding: u16) -> Self { + self.padding = padding; + self + } + + /// Sets the text size of the [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + pub fn text_size(mut self, size: u16) -> Self { + self.text_size = Some(size); + self + } + + /// Sets the font of the [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + pub fn font(mut self, font: Renderer::Font) -> Self { + self.font = font; + self + } + + /// Sets the style of the [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + pub fn style( + mut self, + style: impl Into<<Renderer as self::Renderer>::Style>, + ) -> Self { + self.style = style.into(); + self + } +} + +impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer> + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq, + [T]: ToOwned<Owned = Vec<T>>, + Message: 'static, + Renderer: self::Renderer + scrollable::Renderer + 'a, +{ + fn width(&self) -> Length { + Length::Shrink + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + use std::f32; + + let limits = limits + .width(self.width) + .height(Length::Shrink) + .pad(f32::from(self.padding)); + + let text_size = self.text_size.unwrap_or(renderer.default_size()); + + let max_width = match self.width { + Length::Shrink => { + let labels = self.options.iter().map(ToString::to_string); + + labels + .map(|label| { + let (width, _) = renderer.measure( + &label, + text_size, + Renderer::Font::default(), + Size::new(f32::INFINITY, f32::INFINITY), + ); + + width.round() as u32 + }) + .max() + .unwrap_or(100) + } + _ => 0, + }; + + let size = { + let intrinsic = Size::new( + max_width as f32 + + f32::from(text_size) + + f32::from(self.padding), + f32::from(text_size), + ); + + limits.resolve(intrinsic).pad(f32::from(self.padding)) + }; + + layout::Node::new(size) + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash as _; + + match self.width { + Length::Shrink => { + self.options + .iter() + .map(ToString::to_string) + .for_each(|label| label.hash(state)); + } + _ => { + self.width.hash(state); + } + } + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + _messages: &mut Vec<Message>, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + if !self.is_open { + match event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) => { + if layout.bounds().contains(cursor_position) { + let selected = self.selected.as_ref(); + + self.menu.open( + self.options + .iter() + .position(|option| Some(option) == selected), + ); + } + } + _ => {} + } + } + } + + fn draw( + &self, + renderer: &mut Renderer, + _defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + self::Renderer::draw( + renderer, + layout.bounds(), + cursor_position, + self.selected.as_ref().map(ToString::to_string), + self.padding, + self.text_size.unwrap_or(renderer.default_size()), + self.font, + &self.style, + ) + } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option<overlay::Element<'_, Message, Renderer>> { + if self.menu.is_open() { + let bounds = layout.bounds(); + + let mut menu = + Menu::new(&mut self.menu, &self.options, &self.on_selected) + .width(bounds.width.round() as u16) + .padding(self.padding) + .font(self.font) + .style(Renderer::menu_style(&self.style)); + + if let Some(text_size) = self.text_size { + menu = menu.text_size(text_size); + } + + Some(menu.overlay(layout.position(), bounds.height)) + } else { + None + } + } +} + +/// The renderer of a [`PickList`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`PickList`] in your user interface. +/// +/// [`PickList`]: struct.PickList.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: text::Renderer + menu::Renderer { + /// The default padding of a [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + const DEFAULT_PADDING: u16; + + /// The [`PickList`] style supported by this renderer. + /// + /// [`PickList`]: struct.PickList.html + type Style: Default; + + /// Returns the style of the [`Menu`] of the [`PickList`]. + /// + /// [`Menu`]: ../../overlay/menu/struct.Menu.html + /// [`PickList`]: struct.PickList.html + fn menu_style( + style: &<Self as Renderer>::Style, + ) -> <Self as menu::Renderer>::Style; + + /// Draws a [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + selected: Option<String>, + padding: u16, + text_size: u16, + font: Self::Font, + style: &<Self as Renderer>::Style, + ) -> Self::Output; +} + +impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>> + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq, + [T]: ToOwned<Owned = Vec<T>>, + Renderer: self::Renderer + 'a, + Message: 'static, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 31f7472f..2b6db224 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -2,8 +2,8 @@ use std::hash::Hash; use crate::{ - layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Widget, + layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Widget, }; use std::u32; @@ -206,6 +206,17 @@ where child.widget.hash_layout(state); } } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option<overlay::Element<'_, Message, Renderer>> { + self.children + .iter_mut() + .zip(layout.children()) + .filter_map(|(child, layout)| child.widget.overlay(layout)) + .next() + } } /// The renderer of a [`Row`]. diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 3c8e5e5b..75e97027 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,7 +1,7 @@ //! Navigate an endless amount of content with a scrollbar. use crate::{ - column, layout, mouse, Align, Clipboard, Column, Element, Event, Hasher, - Layout, Length, Point, Rectangle, Size, Widget, + column, layout, mouse, overlay, Align, Clipboard, Column, Element, Event, + Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; use std::{f32, hash::Hash, u32}; @@ -113,7 +113,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { impl<'a, Message, Renderer> Widget<Message, Renderer> for Scrollable<'a, Message, Renderer> where - Renderer: self::Renderer + column::Renderer, + Renderer: self::Renderer, { fn width(&self) -> Length { Widget::<Message, Renderer>::width(&self.content) @@ -315,6 +315,24 @@ where self.content.hash_layout(state) } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option<overlay::Element<'_, Message, Renderer>> { + let Self { content, state, .. } = self; + + content + .overlay(layout.children().next().unwrap()) + .map(|overlay| { + let bounds = layout.bounds(); + let content_layout = layout.children().next().unwrap(); + let content_bounds = content_layout.bounds(); + let offset = state.offset(bounds, content_bounds); + + overlay.translate(Vector::new(0.0, -(offset as f32))) + }) + } } /// The local state of a [`Scrollable`]. @@ -454,7 +472,7 @@ pub struct Scroller { /// /// [`Scrollable`]: struct.Scrollable.html /// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer + Sized { +pub trait Renderer: column::Renderer + Sized { /// The style supported by this renderer. type Style: Default; @@ -502,7 +520,7 @@ pub trait Renderer: crate::Renderer + Sized { impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>> for Element<'a, Message, Renderer> where - Renderer: 'a + self::Renderer + column::Renderer, + Renderer: 'a + self::Renderer, Message: 'a, { fn from( diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs index e56a8fe1..f1576ffb 100644 --- a/native/src/widget/space.rs +++ b/native/src/widget/space.rs @@ -43,7 +43,7 @@ impl Space { } } -impl<'a, Message, Renderer> Widget<Message, Renderer> for Space +impl<Message, Renderer> Widget<Message, Renderer> for Space where Renderer: self::Renderer, { |