From c901f40fd6c5aa39f4dc056b2b59bc7133b287e6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Apr 2020 12:11:10 +0200 Subject: Introduce `Widget::overlay` :tada: --- examples/custom_widget/src/main.rs | 2 +- examples/geometry/src/main.rs | 2 +- examples/todos/src/main.rs | 4 ++-- graphics/src/renderer.rs | 2 +- graphics/src/widget/canvas.rs | 2 +- native/src/element.rs | 21 ++++++++++++++------- native/src/layout/debugger.rs | 2 +- native/src/lib.rs | 4 +++- native/src/overlay.rs | 33 +++++++++++++++++++++++++++++++++ native/src/widget.rs | 8 ++++++-- native/src/widget/button.rs | 2 +- native/src/widget/checkbox.rs | 2 +- native/src/widget/column.rs | 2 +- native/src/widget/container.rs | 2 +- native/src/widget/image.rs | 2 +- native/src/widget/pane_grid.rs | 2 +- native/src/widget/progress_bar.rs | 3 ++- native/src/widget/radio.rs | 3 ++- native/src/widget/row.rs | 2 +- native/src/widget/scrollable.rs | 2 +- native/src/widget/slider.rs | 2 +- native/src/widget/space.rs | 2 +- native/src/widget/svg.rs | 2 +- native/src/widget/text.rs | 2 +- native/src/widget/text_input.rs | 2 +- 25 files changed, 80 insertions(+), 32 deletions(-) create mode 100644 native/src/overlay.rs diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index bcf896b0..a6068f91 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -25,7 +25,7 @@ mod circle { } } - impl Widget> for Circle + impl<'a, Message, B> Widget<'a, Message, Renderer> for Circle where B: Backend, { diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 71ce0d8c..3795323a 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -27,7 +27,7 @@ mod rainbow { } } - impl Widget> for Rainbow + impl<'a, Message, B> Widget<'a, Message, Renderer> for Rainbow where B: Backend, { diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index c9cbcc69..5713a6f2 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -425,7 +425,7 @@ impl Filter { } } -fn loading_message() -> Element<'static, Message> { +fn loading_message<'a>() -> Element<'a, Message> { Container::new( Text::new("Loading...") .horizontal_alignment(HorizontalAlignment::Center) @@ -437,7 +437,7 @@ fn loading_message() -> Element<'static, Message> { .into() } -fn empty_message(message: &str) -> Element<'static, Message> { +fn empty_message<'a>(message: &str) -> Element<'a, Message> { Container::new( Text::new(message) .width(Length::Fill) diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index c9360f3a..44bacd4e 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -62,7 +62,7 @@ where fn explain( &mut self, defaults: &Defaults, - widget: &dyn Widget, + widget: &dyn Widget<'_, Message, Self>, layout: Layout<'_>, cursor_position: Point, color: Color, diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index b8466239..0257f819 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -134,7 +134,7 @@ impl> Canvas { } } -impl Widget> for Canvas +impl<'a, Message, P, B> Widget<'a, Message, Renderer> for Canvas where P: Program, B: Backend, diff --git a/native/src/element.rs b/native/src/element.rs index 73e39012..c6d65550 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -1,5 +1,6 @@ use crate::{ - layout, Clipboard, Color, Event, Hasher, Layout, Length, Point, Widget, + layout, Clipboard, Color, Event, Hasher, Layout, Length, Overlay, Point, + Widget, }; /// A generic [`Widget`]. @@ -15,7 +16,7 @@ use crate::{ /// [`Element`]: struct.Element.html #[allow(missing_debug_implementations)] pub struct Element<'a, Message, Renderer> { - pub(crate) widget: Box + 'a>, + pub(crate) widget: Box + 'a>, } impl<'a, Message, Renderer> Element<'a, Message, Renderer> @@ -27,7 +28,7 @@ where /// [`Element`]: struct.Element.html /// [`Widget`]: widget/trait.Widget.html pub fn new( - widget: impl Widget + 'a, + widget: impl Widget<'a, Message, Renderer> + 'a, ) -> Element<'a, Message, Renderer> { Element { widget: Box::new(widget), @@ -270,16 +271,22 @@ where pub fn hash_layout(&self, state: &mut Hasher) { self.widget.hash_layout(state); } + + pub fn overlay( + &mut self, + ) -> Option + 'a>> { + self.widget.overlay() + } } struct Map<'a, A, B, Renderer> { - widget: Box + 'a>, + widget: Box + 'a>, mapper: Box B>, } impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { pub fn new( - widget: Box + 'a>, + widget: Box + 'a>, mapper: F, ) -> Map<'a, A, B, Renderer> where @@ -292,7 +299,7 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { } } -impl<'a, A, B, Renderer> Widget for Map<'a, A, B, Renderer> +impl<'a, A, B, Renderer> Widget<'a, B, Renderer> for Map<'a, A, B, Renderer> where Renderer: crate::Renderer, { @@ -367,7 +374,7 @@ where } } -impl<'a, Message, Renderer> Widget +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Explain<'a, Message, Renderer> where Renderer: crate::Renderer + layout::Debugger, diff --git a/native/src/layout/debugger.rs b/native/src/layout/debugger.rs index e4b21609..9c58f4f1 100644 --- a/native/src/layout/debugger.rs +++ b/native/src/layout/debugger.rs @@ -18,7 +18,7 @@ pub trait Debugger: Renderer { fn explain( &mut self, defaults: &Self::Defaults, - widget: &dyn Widget, + widget: &dyn Widget<'_, Message, Self>, layout: Layout<'_>, cursor_position: Point, color: Color, diff --git a/native/src/lib.rs b/native/src/lib.rs index b67ff2a1..dbec068d 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -30,7 +30,7 @@ //! [`Widget`]: widget/trait.Widget.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] @@ -48,6 +48,7 @@ mod clipboard; mod element; mod event; mod hasher; +mod overlay; mod runtime; mod user_interface; @@ -75,6 +76,7 @@ pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; +pub use overlay::Overlay; pub use program::Program; pub use renderer::Renderer; pub use runtime::Runtime; diff --git a/native/src/overlay.rs b/native/src/overlay.rs new file mode 100644 index 00000000..d34432a4 --- /dev/null +++ b/native/src/overlay.rs @@ -0,0 +1,33 @@ +use crate::{layout, Clipboard, Event, Hasher, Layout, Point}; + +pub trait Overlay +where + Renderer: crate::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node; + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output; + + fn hash_layout(&self, state: &mut Hasher); + + fn on_event( + &mut self, + _event: Event, + _layout: Layout<'_>, + _cursor_position: Point, + _messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + } +} diff --git a/native/src/widget.rs b/native/src/widget.rs index 4453145b..dd1a97d2 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -67,7 +67,7 @@ pub use text::Text; #[doc(no_inline)] pub use text_input::TextInput; -use crate::{layout, Clipboard, Event, Hasher, Layout, Length, Point}; +use crate::{layout, Clipboard, Event, Hasher, Layout, Length, Overlay, Point}; /// A component that displays information and allows interaction. /// @@ -94,7 +94,7 @@ use crate::{layout, Clipboard, Event, Hasher, Layout, Length, Point}; /// [`geometry`]: https://github.com/hecrj/iced/tree/0.1/examples/geometry /// [`lyon`]: https://github.com/nical/lyon /// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.1/wgpu -pub trait Widget +pub trait Widget<'a, Message, Renderer> where Renderer: crate::Renderer, { @@ -175,4 +175,8 @@ where _clipboard: Option<&dyn Clipboard>, ) { } + + fn overlay(&mut self) -> Option + 'a>> { + None + } } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index c932da2b..72db808b 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -139,7 +139,7 @@ impl State { } } -impl<'a, Message, Renderer> Widget +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Button<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 44962288..82fd6d1f 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -106,7 +106,7 @@ impl } } -impl Widget +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Checkbox where Renderer: self::Renderer + text::Renderer + row::Renderer, diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 259a7e6e..6afc94d6 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -121,7 +121,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { } } -impl<'a, Message, Renderer> Widget +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Column<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 2590fe3b..3ad12eeb 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -129,7 +129,7 @@ where } } -impl<'a, Message, Renderer> Widget +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Container<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 132f249d..b7c8f4ff 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -54,7 +54,7 @@ impl Image { } } -impl Widget for Image +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Image where Renderer: self::Renderer, { diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index a8deef0b..faf8da4e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -387,7 +387,7 @@ pub struct KeyPressEvent { pub modifiers: keyboard::ModifiersState, } -impl<'a, Message, Renderer> Widget +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for PaneGrid<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index 5ab76d47..93d86371 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -70,7 +70,8 @@ impl ProgressBar { } } -impl Widget for ProgressBar +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> + for ProgressBar where Renderer: self::Renderer, { diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 5b8d00e9..0d88c740 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -121,7 +121,8 @@ impl } } -impl Widget for Radio +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> + for Radio where Renderer: self::Renderer + text::Renderer + row::Renderer, Message: Clone, diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 31f7472f..1ebbac6b 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -122,7 +122,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { } } -impl<'a, Message, Renderer> Widget +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Row<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 3c8e5e5b..d7ad98e6 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -110,7 +110,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { } } -impl<'a, Message, Renderer> Widget +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Scrollable<'a, Message, Renderer> where Renderer: self::Renderer + column::Renderer, diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 70f2b6ac..e0193342 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -154,7 +154,7 @@ impl State { } } -impl<'a, T, Message, Renderer> Widget +impl<'a, T, Message, Renderer> Widget<'a, Message, Renderer> for Slider<'a, T, Message, Renderer> where T: Copy + Into + num_traits::FromPrimitive, diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs index e56a8fe1..8ada40ed 100644 --- a/native/src/widget/space.rs +++ b/native/src/widget/space.rs @@ -43,7 +43,7 @@ impl Space { } } -impl<'a, Message, Renderer> Widget for Space +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Space where Renderer: self::Renderer, { diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index 114d5e41..3e45aaf6 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -60,7 +60,7 @@ impl Svg { } } -impl Widget for Svg +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Svg where Renderer: self::Renderer, { diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 48a69e34..7f75eb9c 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -112,7 +112,7 @@ impl Text { } } -impl Widget for Text +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Text where Renderer: self::Renderer, { diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 3f415101..63be6019 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -165,7 +165,7 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> { } } -impl<'a, Message, Renderer> Widget +impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for TextInput<'a, Message, Renderer> where Renderer: self::Renderer, -- cgit From f064f0482b653a1fbee4afbddcecf91e3a399004 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 16 Apr 2020 13:22:00 +0200 Subject: Introduce `Layer` trait --- graphics/src/renderer.rs | 29 +++++++- native/src/element.rs | 5 +- native/src/layer.rs | 34 ++++++++++ native/src/layout.rs | 7 ++ native/src/lib.rs | 2 + native/src/overlay.rs | 37 +++++++--- native/src/renderer.rs | 9 ++- native/src/renderer/null.rs | 3 + native/src/user_interface.rs | 149 +++++++++++++++++++++++++++++++---------- native/src/widget.rs | 5 +- native/src/widget/column.rs | 15 ++++- native/src/widget/container.rs | 11 ++- 12 files changed, 252 insertions(+), 54 deletions(-) create mode 100644 native/src/layer.rs diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 44bacd4e..771f436c 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,7 +1,9 @@ use crate::{Backend, Defaults, Primitive}; use iced_native::layout::{self, Layout}; use iced_native::mouse; -use iced_native::{Background, Color, Element, Point, Widget}; +use iced_native::{ + Background, Color, Element, Point, Rectangle, Vector, Widget, +}; /// A backend-agnostic renderer that supports all the built-in widgets. #[derive(Debug)] @@ -53,6 +55,31 @@ where layout } + + fn overlay( + &mut self, + (base_primitive, base_cursor): (Primitive, mouse::Interaction), + (overlay_primitives, overlay_cursor): (Primitive, mouse::Interaction), + overlay_bounds: Rectangle, + ) -> (Primitive, mouse::Interaction) { + ( + Primitive::Group { + primitives: vec![ + base_primitive, + Primitive::Clip { + bounds: overlay_bounds, + offset: Vector::new(0, 0), + content: Box::new(overlay_primitives), + }, + ], + }, + if base_cursor > overlay_cursor { + base_cursor + } else { + overlay_cursor + }, + ) + } } impl layout::Debugger for Renderer diff --git a/native/src/element.rs b/native/src/element.rs index c6d65550..01379d2d 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -274,8 +274,9 @@ where pub fn overlay( &mut self, - ) -> Option + 'a>> { - self.widget.overlay() + layout: Layout<'_>, + ) -> Option> { + self.widget.overlay(layout) } } diff --git a/native/src/layer.rs b/native/src/layer.rs new file mode 100644 index 00000000..d89fb4e5 --- /dev/null +++ b/native/src/layer.rs @@ -0,0 +1,34 @@ +use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size}; + +pub trait Layer +where + Renderer: crate::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node; + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output; + + fn hash_layout(&self, state: &mut Hasher, position: Point); + + fn on_event( + &mut self, + _event: Event, + _layout: Layout<'_>, + _cursor_position: Point, + _messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + } +} diff --git a/native/src/layout.rs b/native/src/layout.rs index 4a3ab94a..178f0f24 100644 --- a/native/src/layout.rs +++ b/native/src/layout.rs @@ -34,6 +34,13 @@ impl<'a> Layout<'a> { } } + /// Gets the position of the [`Layout`]. + /// + /// [`Layout`]: struct.Layout.html + pub fn position(&self) -> Point { + self.position + } + /// Gets the bounds of the [`Layout`]. /// /// The returned [`Rectangle`] describes the position and size of a diff --git a/native/src/lib.rs b/native/src/lib.rs index dbec068d..6974c2bd 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -48,6 +48,7 @@ mod clipboard; mod element; mod event; mod hasher; +mod layer; mod overlay; mod runtime; mod user_interface; @@ -75,6 +76,7 @@ pub use debug::Debug; pub use element::Element; pub use event::Event; pub use hasher::Hasher; +pub use layer::Layer; pub use layout::Layout; pub use overlay::Overlay; pub use program::Program; diff --git a/native/src/overlay.rs b/native/src/overlay.rs index d34432a4..a4bd5ea3 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -1,26 +1,41 @@ -use crate::{layout, Clipboard, Event, Hasher, Layout, Point}; +use crate::{layout, Clipboard, Event, Hasher, Layer, Layout, Point, Size}; -pub trait Overlay +#[allow(missing_debug_implementations)] +pub struct Overlay<'a, Message, Renderer> { + position: Point, + layer: Box + 'a>, +} + +impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> where Renderer: crate::Renderer, { - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node; + pub fn new( + position: Point, + layer: Box + 'a>, + ) -> Self { + Self { position, layer } + } - fn draw( + pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node { + self.layer.layout(renderer, bounds, self.position) + } + + pub fn draw( &self, renderer: &mut Renderer, defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Output; + ) -> Renderer::Output { + self.layer.draw(renderer, defaults, layout, cursor_position) + } - fn hash_layout(&self, state: &mut Hasher); + pub fn hash_layout(&self, state: &mut Hasher) { + self.layer.hash_layout(state, self.position); + } - fn on_event( + pub fn on_event( &mut self, _event: Event, _layout: Layout<'_>, diff --git a/native/src/renderer.rs b/native/src/renderer.rs index a16df72b..29a091a4 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -25,7 +25,7 @@ mod null; #[cfg(debug_assertions)] pub use null::Null; -use crate::{layout, Element}; +use crate::{layout, Element, Rectangle}; /// A component that can take the state of a user interface and produce an /// output for its users. @@ -56,4 +56,11 @@ pub trait Renderer: Sized { ) -> layout::Node { element.layout(self, limits) } + + fn overlay( + &mut self, + base: Self::Output, + overlay: Self::Output, + overlay_bounds: Rectangle, + ) -> Self::Output; } diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 5fd3627b..bd6aca29 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -22,6 +22,9 @@ impl Null { impl Renderer for Null { type Output = (); type Defaults = (); + + fn overlay(&mut self, _base: (), _overlay: (), _overlay_bounds: Rectangle) { + } } impl column::Renderer for Null { diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index b9646043..6758bce3 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,4 +1,4 @@ -use crate::{layout, Clipboard, Element, Event, Layout, Point, Size}; +use crate::{layout, Clipboard, Element, Event, Layout, Overlay, Point, Size}; use std::hash::Hasher; @@ -19,12 +19,17 @@ use std::hash::Hasher; /// [`UserInterface`]: struct.UserInterface.html #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { - hash: u64, - root: Element<'a, Message, Renderer>, - layout: layout::Node, + base: Layer>, + overlay: Option>>, bounds: Size, } +struct Layer { + root: T, + layout: layout::Node, + hash: u64, +} + impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> where Renderer: crate::Renderer, @@ -92,27 +97,45 @@ where cache: Cache, renderer: &mut Renderer, ) -> Self { - let root = root.into(); + let mut root = root.into(); - let hash = { - let hasher = &mut crate::Hasher::default(); - root.hash_layout(hasher); + let (base, overlay) = { + let hash = { + let hasher = &mut crate::Hasher::default(); + root.hash_layout(hasher); - hasher.finish() - }; + hasher.finish() + }; - let layout_is_cached = hash == cache.hash && bounds == cache.bounds; + let layout_is_cached = hash == cache.hash && bounds == cache.bounds; - let layout = if layout_is_cached { - cache.layout - } else { - renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)) + let layout = if layout_is_cached { + cache.layout + } else { + renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)) + }; + + let overlay = root.overlay(Layout::new(&layout)); + + (Layer { root, layout, hash }, overlay) }; + let overlay = overlay.map(|root| { + let hash = { + let hasher = &mut crate::Hasher::default(); + root.hash_layout(hasher); + + hasher.finish() + }; + + let layout = root.layout(&renderer, bounds); + + Layer { root, layout, hash } + }); + UserInterface { - hash, - root, - layout, + base, + overlay, bounds, } } @@ -193,14 +216,42 @@ where let mut messages = Vec::new(); for event in events { - self.root.widget.on_event( - event, - Layout::new(&self.layout), - cursor_position, - &mut messages, - renderer, - clipboard, - ); + if let Some(overlay) = &mut self.overlay { + overlay.root.on_event( + event.clone(), + Layout::new(&overlay.layout), + cursor_position, + &mut messages, + renderer, + clipboard, + ); + + let base_cursor = + if overlay.layout.bounds().contains(cursor_position) { + // TODO: Encode cursor availability + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + + self.base.root.widget.on_event( + event, + Layout::new(&self.base.layout), + base_cursor, + &mut messages, + renderer, + clipboard, + ); + } else { + self.base.root.widget.on_event( + event, + Layout::new(&self.base.layout), + cursor_position, + &mut messages, + renderer, + clipboard, + ); + } } messages @@ -280,12 +331,42 @@ where renderer: &mut Renderer, cursor_position: Point, ) -> Renderer::Output { - self.root.widget.draw( - renderer, - &Renderer::Defaults::default(), - Layout::new(&self.layout), - cursor_position, - ) + if let Some(overlay) = &self.overlay { + let overlay_bounds = overlay.layout.bounds(); + + let base_cursor = if overlay_bounds.contains(cursor_position) { + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + + let base_primitives = self.base.root.widget.draw( + renderer, + &Renderer::Defaults::default(), + Layout::new(&self.base.layout), + base_cursor, + ); + + let overlay_primitives = overlay.root.draw( + renderer, + &Renderer::Defaults::default(), + Layout::new(&overlay.layout), + cursor_position, + ); + + renderer.overlay( + base_primitives, + overlay_primitives, + overlay_bounds, + ) + } else { + self.base.root.widget.draw( + renderer, + &Renderer::Defaults::default(), + Layout::new(&self.base.layout), + cursor_position, + ) + } } /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the @@ -295,8 +376,8 @@ where /// [`UserInterface`]: struct.UserInterface.html pub fn into_cache(self) -> Cache { Cache { - hash: self.hash, - layout: self.layout, + hash: self.base.hash, + layout: self.base.layout, bounds: self.bounds, } } diff --git a/native/src/widget.rs b/native/src/widget.rs index dd1a97d2..0494636f 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -176,7 +176,10 @@ where ) { } - fn overlay(&mut self) -> Option + 'a>> { + fn overlay( + &mut self, + _layout: Layout<'_>, + ) -> Option> { None } } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 6afc94d6..9a6dbdb3 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, Align, Clipboard, Element, Event, Hasher, Layout, Length, Overlay, + Point, Widget, }; use std::u32; @@ -204,6 +204,17 @@ where child.widget.hash_layout(state); } } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + 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 3ad12eeb..3bdb1a27 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, Align, Clipboard, Element, Event, Hasher, Layout, Length, Overlay, + Point, Rectangle, Widget, }; use std::u32; @@ -214,6 +214,13 @@ where self.content.hash_layout(state); } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + self.content.overlay(layout.children().next().unwrap()) + } } /// The renderer of a [`Container`]. -- cgit From b1afadf1a2162e236525c466b6b3099a2623a2de Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 16 Apr 2020 13:22:55 +0200 Subject: Draft `combo_box` example to test overlay logic --- Cargo.toml | 1 + examples/combo_box/Cargo.toml | 11 +++ examples/combo_box/README.md | 18 +++++ examples/combo_box/src/main.rs | 163 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 examples/combo_box/Cargo.toml create mode 100644 examples/combo_box/README.md create mode 100644 examples/combo_box/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 9ab57bc8..c62b5c9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ members = [ "examples/bezier_tool", "examples/clock", "examples/color_palette", + "examples/combo_box", "examples/counter", "examples/custom_widget", "examples/download_progress", diff --git a/examples/combo_box/Cargo.toml b/examples/combo_box/Cargo.toml new file mode 100644 index 00000000..7e1e4133 --- /dev/null +++ b/examples/combo_box/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "combo_box" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +iced_native = { path = "../../native" } +iced_wgpu = { path = "../../wgpu" } diff --git a/examples/combo_box/README.md b/examples/combo_box/README.md new file mode 100644 index 00000000..4d9fc5b9 --- /dev/null +++ b/examples/combo_box/README.md @@ -0,0 +1,18 @@ +## Counter + +The classic counter example explained in the [`README`](../../README.md). + +The __[`main`]__ file contains all the code of the example. + + + +You can run it with `cargo run`: +``` +cargo run --package counter +``` + +[`main`]: src/main.rs diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs new file mode 100644 index 00000000..742378c0 --- /dev/null +++ b/examples/combo_box/src/main.rs @@ -0,0 +1,163 @@ +mod combo_box { + use iced_native::{ + layout, mouse, Background, Color, Element, Hasher, Layer, Layout, + Length, Overlay, Point, Size, Vector, Widget, + }; + use iced_wgpu::{Defaults, Primitive, Renderer}; + + pub struct ComboBox; + + impl ComboBox { + pub fn new() -> Self { + Self + } + } + + impl<'a, Message> Widget<'a, Message, Renderer> for ComboBox { + fn width(&self) -> Length { + Length::Shrink + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + _renderer: &Renderer, + _limits: &layout::Limits, + ) -> layout::Node { + layout::Node::new(Size::new(50.0, 50.0)) + } + + fn hash_layout(&self, _state: &mut Hasher) {} + + fn draw( + &self, + _renderer: &mut Renderer, + _defaults: &Defaults, + layout: Layout<'_>, + _cursor_position: Point, + ) -> (Primitive, mouse::Interaction) { + let primitive = Primitive::Quad { + bounds: layout.bounds(), + background: Background::Color(Color::BLACK), + border_width: 0, + border_radius: 0, + border_color: Color::TRANSPARENT, + }; + + (primitive, mouse::Interaction::default()) + } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + Some(Overlay::new(layout.position(), Box::new(Menu))) + } + } + + impl<'a, Message> Into> for ComboBox { + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } + } + + pub struct Menu; + + impl Layer for Menu { + fn layout( + &self, + _renderer: &Renderer, + _bounds: Size, + position: Point, + ) -> layout::Node { + let mut node = layout::Node::new(Size::new(100.0, 100.0)); + + node.move_to(position + Vector::new(25.0, 25.0)); + + node + } + + fn hash_layout(&self, state: &mut Hasher, position: Point) { + use std::hash::Hash; + + (position.x as u32).hash(state); + (position.y as u32).hash(state); + } + + fn draw( + &self, + _renderer: &mut Renderer, + _defaults: &Defaults, + layout: Layout<'_>, + _cursor_position: Point, + ) -> (Primitive, mouse::Interaction) { + let primitive = Primitive::Quad { + bounds: layout.bounds(), + background: Background::Color(Color { + r: 0.0, + g: 0.0, + b: 1.0, + a: 0.5, + }), + border_width: 0, + border_radius: 0, + border_color: Color::TRANSPARENT, + }; + + (primitive, mouse::Interaction::default()) + } + } +} + +pub use combo_box::ComboBox; + +use iced::{ + button, Button, Column, Container, Element, Length, Sandbox, Settings, Text, +}; + +pub fn main() { + Example::run(Settings::default()) +} + +#[derive(Default)] +struct Example { + button: button::State, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + ButtonPressed, +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Combo box - Iced") + } + + fn update(&mut self, _message: Message) {} + + fn view(&mut self) -> Element { + let combo_box = ComboBox::new(); + + let button = Button::new(&mut self.button, Text::new("Press me!")) + .on_press(Message::ButtonPressed); + + let content = Column::new().spacing(10).push(combo_box).push(button); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} -- cgit From afd9274de26ccf65285df02007b4ddb697bea9a3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 18 Apr 2020 14:42:48 +0200 Subject: Draft `ComboBox` and `Menu` layer --- examples/combo_box/src/main.rs | 196 ++++++++++--------------- glow/src/lib.rs | 2 +- glow/src/widget.rs | 3 + glow/src/widget/combo_box.rs | 3 + graphics/src/layer.rs | 2 + graphics/src/layer/menu.rs | 102 +++++++++++++ graphics/src/lib.rs | 2 +- graphics/src/widget.rs | 1 + graphics/src/widget/combo_box.rs | 67 +++++++++ native/src/element.rs | 25 +++- native/src/layer.rs | 5 + native/src/layer/menu.rs | 305 +++++++++++++++++++++++++++++++++++++++ native/src/lib.rs | 4 +- native/src/overlay.rs | 100 ++++++++++++- native/src/user_interface.rs | 16 +- native/src/widget.rs | 3 + native/src/widget/combo_box.rs | 256 ++++++++++++++++++++++++++++++++ native/src/widget/scrollable.rs | 6 +- src/widget.rs | 10 +- wgpu/src/lib.rs | 2 +- wgpu/src/widget.rs | 3 + wgpu/src/widget/combo_box.rs | 3 + 22 files changed, 966 insertions(+), 150 deletions(-) create mode 100644 glow/src/widget/combo_box.rs create mode 100644 graphics/src/layer/menu.rs create mode 100644 graphics/src/widget/combo_box.rs create mode 100644 native/src/layer/menu.rs create mode 100644 native/src/widget/combo_box.rs create mode 100644 wgpu/src/widget/combo_box.rs diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs index 742378c0..75a3f713 100644 --- a/examples/combo_box/src/main.rs +++ b/examples/combo_box/src/main.rs @@ -1,121 +1,6 @@ -mod combo_box { - use iced_native::{ - layout, mouse, Background, Color, Element, Hasher, Layer, Layout, - Length, Overlay, Point, Size, Vector, Widget, - }; - use iced_wgpu::{Defaults, Primitive, Renderer}; - - pub struct ComboBox; - - impl ComboBox { - pub fn new() -> Self { - Self - } - } - - impl<'a, Message> Widget<'a, Message, Renderer> for ComboBox { - fn width(&self) -> Length { - Length::Shrink - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - _renderer: &Renderer, - _limits: &layout::Limits, - ) -> layout::Node { - layout::Node::new(Size::new(50.0, 50.0)) - } - - fn hash_layout(&self, _state: &mut Hasher) {} - - fn draw( - &self, - _renderer: &mut Renderer, - _defaults: &Defaults, - layout: Layout<'_>, - _cursor_position: Point, - ) -> (Primitive, mouse::Interaction) { - let primitive = Primitive::Quad { - bounds: layout.bounds(), - background: Background::Color(Color::BLACK), - border_width: 0, - border_radius: 0, - border_color: Color::TRANSPARENT, - }; - - (primitive, mouse::Interaction::default()) - } - - fn overlay( - &mut self, - layout: Layout<'_>, - ) -> Option> { - Some(Overlay::new(layout.position(), Box::new(Menu))) - } - } - - impl<'a, Message> Into> for ComboBox { - fn into(self) -> Element<'a, Message, Renderer> { - Element::new(self) - } - } - - pub struct Menu; - - impl Layer for Menu { - fn layout( - &self, - _renderer: &Renderer, - _bounds: Size, - position: Point, - ) -> layout::Node { - let mut node = layout::Node::new(Size::new(100.0, 100.0)); - - node.move_to(position + Vector::new(25.0, 25.0)); - - node - } - - fn hash_layout(&self, state: &mut Hasher, position: Point) { - use std::hash::Hash; - - (position.x as u32).hash(state); - (position.y as u32).hash(state); - } - - fn draw( - &self, - _renderer: &mut Renderer, - _defaults: &Defaults, - layout: Layout<'_>, - _cursor_position: Point, - ) -> (Primitive, mouse::Interaction) { - let primitive = Primitive::Quad { - bounds: layout.bounds(), - background: Background::Color(Color { - r: 0.0, - g: 0.0, - b: 1.0, - a: 0.5, - }), - border_width: 0, - border_radius: 0, - border_color: Color::TRANSPARENT, - }; - - (primitive, mouse::Interaction::default()) - } - } -} - -pub use combo_box::ComboBox; - use iced::{ - button, Button, Column, Container, Element, Length, Sandbox, Settings, Text, + button, combo_box, Button, Column, ComboBox, Container, Element, Length, + Sandbox, Settings, Text, }; pub fn main() { @@ -125,11 +10,14 @@ pub fn main() { #[derive(Default)] struct Example { button: button::State, + combo_box: combo_box::State, + selected_language: Language, } #[derive(Debug, Clone, Copy)] enum Message { ButtonPressed, + LanguageSelected(Language), } impl Sandbox for Example { @@ -143,15 +31,36 @@ impl Sandbox for Example { String::from("Combo box - Iced") } - fn update(&mut self, _message: Message) {} + fn update(&mut self, message: Message) { + match message { + Message::ButtonPressed => {} + Message::LanguageSelected(language) => { + self.selected_language = language; + } + } + } fn view(&mut self) -> Element { - let combo_box = ComboBox::new(); + let combo_box = ComboBox::new( + &mut self.combo_box, + &Language::ALL[..], + Some(self.selected_language), + Message::LanguageSelected, + ); let button = Button::new(&mut self.button, Text::new("Press me!")) .on_press(Message::ButtonPressed); - let content = Column::new().spacing(10).push(combo_box).push(button); + let mut content = Column::new() + .spacing(10) + .push(Text::new("Which is your favorite language?")) + .push(combo_box); + + if self.selected_language == Language::Javascript { + content = content.push(Text::new("You are wrong!")); + } + + content = content.push(button); Container::new(content) .width(Length::Fill) @@ -161,3 +70,50 @@ impl Sandbox for Example { .into() } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Language { + Rust, + Elm, + Ruby, + Haskell, + C, + Javascript, + Other, +} + +impl Language { + const ALL: [Language; 7] = [ + Language::C, + Language::Elm, + Language::Ruby, + Language::Haskell, + Language::Rust, + Language::Javascript, + Language::Other, + ]; +} + +impl Default for Language { + fn default() -> Language { + Language::Rust + } +} + +impl std::fmt::Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Language::Rust => "Rust", + Language::Elm => "Elm", + Language::Ruby => "Ruby", + Language::Haskell => "Haskell", + Language::C => "C", + Language::Javascript => "Javascript", + Language::Other => "Some other language", + } + ) + } +} diff --git a/glow/src/lib.rs b/glow/src/lib.rs index a6c8a75a..bdd854e3 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -2,7 +2,7 @@ //! //! [`glow`]: https://github.com/grovesNL/glow //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(rust_2018_idioms)] diff --git a/glow/src/widget.rs b/glow/src/widget.rs index 9968092b..c8f16725 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -11,6 +11,7 @@ use crate::Renderer; pub mod button; pub mod checkbox; +pub mod combo_box; pub mod container; pub mod pane_grid; pub mod progress_bar; @@ -24,6 +25,8 @@ pub use button::Button; #[doc(no_inline)] pub use checkbox::Checkbox; #[doc(no_inline)] +pub use combo_box::ComboBox; +#[doc(no_inline)] pub use container::Container; #[doc(no_inline)] pub use pane_grid::PaneGrid; diff --git a/glow/src/widget/combo_box.rs b/glow/src/widget/combo_box.rs new file mode 100644 index 00000000..bb3931ef --- /dev/null +++ b/glow/src/widget/combo_box.rs @@ -0,0 +1,3 @@ +pub use iced_native::combo_box::State; + +pub type ComboBox<'a, T, Message> = iced_native::ComboBox<'a, T, Message>; diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index 6aca738e..ddf835a4 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -1,4 +1,6 @@ //! Organize rendering primitives into a flattened list of layers. +mod menu; + use crate::image; use crate::svg; use crate::triangle; diff --git a/graphics/src/layer/menu.rs b/graphics/src/layer/menu.rs new file mode 100644 index 00000000..e94ef964 --- /dev/null +++ b/graphics/src/layer/menu.rs @@ -0,0 +1,102 @@ +use crate::backend::Backend; +use crate::{Primitive, Renderer}; +use iced_native::{ + layer, mouse, Background, Color, Font, HorizontalAlignment, Point, + Rectangle, VerticalAlignment, +}; + +impl layer::menu::Renderer for Renderer +where + B: Backend, +{ + fn decorate( + &mut self, + bounds: Rectangle, + _cursor_position: Point, + (primitives, mouse_cursor): Self::Output, + ) -> Self::Output { + ( + Primitive::Group { + primitives: vec![ + Primitive::Quad { + bounds, + background: Background::Color( + [0.87, 0.87, 0.87].into(), + ), + border_color: [0.7, 0.7, 0.7].into(), + border_width: 1, + border_radius: 0, + }, + primitives, + ], + }, + mouse_cursor, + ) + } + + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + options: &[T], + hovered_option: Option, + text_size: u16, + padding: u16, + ) -> Self::Output { + use std::f32; + + let is_mouse_over = bounds.contains(cursor_position); + + let mut primitives = Vec::new(); + + for (i, option) in options.iter().enumerate() { + let is_selected = hovered_option == Some(i); + + let bounds = Rectangle { + x: bounds.x, + y: bounds.y + + ((text_size as usize + padding as usize * 2) * i) as f32, + width: bounds.width, + height: f32::from(text_size + padding * 2), + }; + + if is_selected { + primitives.push(Primitive::Quad { + bounds, + background: Background::Color([0.4, 0.4, 1.0].into()), + border_color: Color::TRANSPARENT, + border_width: 0, + border_radius: 0, + }); + } + + primitives.push(Primitive::Text { + content: option.to_string(), + bounds: Rectangle { + x: bounds.x + f32::from(padding), + y: bounds.center_y(), + width: f32::INFINITY, + ..bounds + }, + size: f32::from(text_size), + font: Font::Default, + color: if is_selected { + Color::WHITE + } else { + Color::BLACK + }, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Center, + }); + } + + ( + Primitive::Group { primitives }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 38d8dffa..bad35ccf 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -2,7 +2,7 @@ //! for [`iced`]. //! //! [`iced`]: https://github.com/hecrj/iced -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index 1f6d6559..a0d06999 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -9,6 +9,7 @@ //! ``` pub mod button; pub mod checkbox; +pub mod combo_box; pub mod container; pub mod image; pub mod pane_grid; diff --git a/graphics/src/widget/combo_box.rs b/graphics/src/widget/combo_box.rs new file mode 100644 index 00000000..27ea762a --- /dev/null +++ b/graphics/src/widget/combo_box.rs @@ -0,0 +1,67 @@ +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::{ + mouse, Background, Color, Font, HorizontalAlignment, Point, Rectangle, + VerticalAlignment, +}; + +pub use iced_native::ComboBox; + +impl iced_native::combo_box::Renderer for Renderer +where + B: Backend + backend::Text, +{ + const DEFAULT_PADDING: u16 = 5; + + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + selected: Option, + text_size: u16, + padding: u16, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let background = Primitive::Quad { + bounds, + background: Background::Color([0.87, 0.87, 0.87].into()), + border_color: if is_mouse_over { + Color::BLACK + } else { + [0.7, 0.7, 0.7].into() + }, + border_width: 1, + border_radius: 0, + }; + + ( + if let Some(label) = selected { + let label = Primitive::Text { + content: label, + size: f32::from(text_size), + font: Font::Default, + color: Color::BLACK, + bounds: Rectangle { + x: bounds.x + f32::from(padding), + y: bounds.center_y(), + ..bounds + }, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Center, + }; + + Primitive::Group { + primitives: vec![background, label], + } + } else { + background + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/native/src/element.rs b/native/src/element.rs index 01379d2d..c881871a 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -2,6 +2,7 @@ use crate::{ layout, Clipboard, Color, Event, Hasher, Layout, Length, Overlay, Point, Widget, }; +use std::rc::Rc; /// A generic [`Widget`]. /// @@ -282,7 +283,7 @@ where struct Map<'a, A, B, Renderer> { widget: Box + 'a>, - mapper: Box B>, + mapper: Rc B>, } impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { @@ -295,14 +296,16 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { { Map { widget, - mapper: Box::new(mapper), + mapper: Rc::new(mapper), } } } impl<'a, A, B, Renderer> Widget<'a, B, Renderer> for Map<'a, A, B, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::Renderer + 'a, + A: 'static, + B: 'static, { fn width(&self) -> Length { self.widget.width() @@ -359,6 +362,15 @@ where fn hash_layout(&self, state: &mut Hasher) { self.widget.hash_layout(state); } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + self.widget + .overlay(layout) + .map(|overlay| overlay.map(self.mapper.clone())) + } } struct Explain<'a, Message, Renderer: crate::Renderer> { @@ -434,4 +446,11 @@ where fn hash_layout(&self, state: &mut Hasher) { self.element.widget.hash_layout(state); } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + self.element.overlay(layout) + } } diff --git a/native/src/layer.rs b/native/src/layer.rs index d89fb4e5..eacfe94b 100644 --- a/native/src/layer.rs +++ b/native/src/layer.rs @@ -1,3 +1,8 @@ +pub mod menu; + +#[doc(no_inline)] +pub use menu::Menu; + use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size}; pub trait Layer diff --git a/native/src/layer/menu.rs b/native/src/layer/menu.rs new file mode 100644 index 00000000..9e26767b --- /dev/null +++ b/native/src/layer/menu.rs @@ -0,0 +1,305 @@ +use crate::{ + container, layout, mouse, scrollable, Clipboard, Container, Element, Event, + Hasher, Layer, Layout, Length, Point, Rectangle, Scrollable, Size, Widget, +}; +use std::borrow::Cow; + +pub struct Menu<'a, Message, Renderer: self::Renderer> { + container: Container<'a, Message, Renderer>, + is_open: &'a mut bool, + width: u16, +} + +#[derive(Default)] +pub struct State { + scrollable: scrollable::State, + hovered_option: Option, + is_open: bool, +} + +impl State { + pub fn is_open(&self) -> bool { + self.is_open + } + + pub fn open(&mut self, hovered_option: Option) { + self.is_open = true; + self.hovered_option = hovered_option; + } +} + +impl<'a, Message, Renderer: self::Renderer> Menu<'a, Message, Renderer> +where + Message: 'static, + Renderer: 'a, +{ + pub fn new( + state: &'a mut State, + options: impl Into>, + on_selected: Box Message>, + width: u16, + text_size: u16, + padding: u16, + ) -> Self + where + T: Clone + ToString, + [T]: ToOwned>, + { + let container = Container::new( + Scrollable::new(&mut state.scrollable).push(List::new( + &mut state.hovered_option, + options, + on_selected, + text_size, + padding, + )), + ) + .padding(1); + + Self { + container, + is_open: &mut state.is_open, + width, + } + } +} + +impl<'a, Message, Renderer> Layer + for Menu<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + let limits = layout::Limits::new( + Size::ZERO, + Size::new(bounds.width - position.x, bounds.height - position.y), + ) + .width(Length::Units(self.width)); + + let mut node = self.container.layout(renderer, &limits); + + node.move_to(position); + + node + } + + fn hash_layout(&self, state: &mut Hasher, position: Point) { + use std::hash::Hash; + + (position.x as u32).hash(state); + (position.y as u32).hash(state); + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, + ) { + let bounds = layout.bounds(); + let current_messages = messages.len(); + + self.container.on_event( + event.clone(), + layout, + cursor_position, + messages, + renderer, + clipboard, + ); + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + if !bounds.contains(cursor_position) + || current_messages < messages.len() => + { + *self.is_open = false; + } + _ => {} + } + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + let primitives = + self.container + .draw(renderer, defaults, layout, cursor_position); + + renderer.decorate(layout.bounds(), cursor_position, primitives) + } +} + +struct List<'a, T, Message> +where + [T]: ToOwned, +{ + hovered_option: &'a mut Option, + options: Cow<'a, [T]>, + on_selected: Box Message>, + text_size: u16, + padding: u16, +} + +impl<'a, T, Message> List<'a, T, Message> +where + [T]: ToOwned, +{ + pub fn new( + hovered_option: &'a mut Option, + options: impl Into>, + on_selected: Box Message>, + text_size: u16, + padding: u16, + ) -> Self { + List { + hovered_option, + options: options.into(), + on_selected, + text_size, + padding, + } + } +} + +impl<'a, T, Message, Renderer> Widget<'a, Message, Renderer> + for List<'a, T, Message> +where + T: ToString + Clone, + [T]: ToOwned, + Renderer: self::Renderer, +{ + fn width(&self) -> Length { + Length::Fill + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + use std::f32; + + let limits = limits.width(Length::Fill).height(Length::Shrink); + + let size = { + let intrinsic = Size::new( + 0.0, + f32::from(self.text_size + self.padding * 2) + * self.options.len() as f32, + ); + + limits.resolve(intrinsic) + }; + + layout::Node::new(size) + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash as _; + + 0.hash(state); + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + if let Some(index) = *self.hovered_option { + if let Some(option) = self.options.get(index) { + messages.push((self.on_selected)(option.clone())); + } + } + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + *self.hovered_option = Some( + ((cursor_position.y - bounds.y) + / f32::from(self.text_size + self.padding * 2)) + as usize, + ); + } + } + _ => {} + } + } + + 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.options, + *self.hovered_option, + self.text_size, + self.padding, + ) + } +} + +pub trait Renderer: scrollable::Renderer + container::Renderer { + fn decorate( + &mut self, + bounds: Rectangle, + cursor_position: Point, + primitive: Self::Output, + ) -> Self::Output; + + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + options: &[T], + hovered_option: Option, + text_size: u16, + padding: u16, + ) -> Self::Output; +} + +impl<'a, T, Message, Renderer> Into> + for List<'a, T, Message> +where + T: ToString + Clone, + [T]: ToOwned, + Message: 'static, + Renderer: self::Renderer, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} diff --git a/native/src/lib.rs b/native/src/lib.rs index 6974c2bd..99d80126 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -31,11 +31,12 @@ //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html //#![deny(missing_docs)] -#![deny(missing_debug_implementations)] +//#![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] pub mod keyboard; +pub mod layer; pub mod layout; pub mod mouse; pub mod program; @@ -48,7 +49,6 @@ mod clipboard; mod element; mod event; mod hasher; -mod layer; mod overlay; mod runtime; mod user_interface; diff --git a/native/src/overlay.rs b/native/src/overlay.rs index a4bd5ea3..d7a1e082 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -1,4 +1,5 @@ use crate::{layout, Clipboard, Event, Hasher, Layer, Layout, Point, Size}; +use std::rc::Rc; #[allow(missing_debug_implementations)] pub struct Overlay<'a, Message, Renderer> { @@ -17,6 +18,18 @@ where Self { position, layer } } + pub fn map(self, f: Rc B>) -> Overlay<'a, B, Renderer> + where + Message: 'static, + Renderer: 'a, + B: 'static, + { + Overlay { + position: self.position, + layer: Box::new(Map::new(self.layer, f)), + } + } + pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node { self.layer.layout(renderer, bounds, self.position) } @@ -37,12 +50,87 @@ where pub fn on_event( &mut self, - _event: Event, - _layout: Layout<'_>, - _cursor_position: Point, - _messages: &mut Vec, - _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, ) { + self.layer.on_event( + event, + layout, + cursor_position, + messages, + renderer, + clipboard, + ) + } +} + +struct Map<'a, A, B, Renderer> { + layer: Box + 'a>, + mapper: Rc B>, +} + +impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { + pub fn new( + layer: Box + 'a>, + mapper: Rc B + 'static>, + ) -> Map<'a, A, B, Renderer> { + Map { layer, mapper } + } +} + +impl<'a, A, B, Renderer> Layer for Map<'a, A, B, Renderer> +where + Renderer: crate::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + self.layer.layout(renderer, bounds, position) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, + ) { + let mut original_messages = Vec::new(); + + self.layer.on_event( + event, + layout, + cursor_position, + &mut original_messages, + renderer, + clipboard, + ); + + original_messages + .drain(..) + .for_each(|message| messages.push((self.mapper)(message))); + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + self.layer.draw(renderer, defaults, layout, cursor_position) + } + + fn hash_layout(&self, state: &mut Hasher, position: Point) { + self.layer.hash_layout(state, position); } } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 6758bce3..12cea684 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -217,6 +217,14 @@ where for event in events { if let Some(overlay) = &mut self.overlay { + let base_cursor = + if overlay.layout.bounds().contains(cursor_position) { + // TODO: Encode cursor availability + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + overlay.root.on_event( event.clone(), Layout::new(&overlay.layout), @@ -226,14 +234,6 @@ where clipboard, ); - let base_cursor = - if overlay.layout.bounds().contains(cursor_position) { - // TODO: Encode cursor availability - Point::new(-1.0, -1.0) - } else { - cursor_position - }; - self.base.root.widget.on_event( event, Layout::new(&self.base.layout), diff --git a/native/src/widget.rs b/native/src/widget.rs index 0494636f..664a0cfd 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -23,6 +23,7 @@ pub mod button; pub mod checkbox; pub mod column; +pub mod combo_box; pub mod container; pub mod image; pub mod pane_grid; @@ -43,6 +44,8 @@ pub use checkbox::Checkbox; #[doc(no_inline)] pub use column::Column; #[doc(no_inline)] +pub use combo_box::ComboBox; +#[doc(no_inline)] pub use container::Container; #[doc(no_inline)] pub use image::Image; diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs new file mode 100644 index 00000000..0b25b836 --- /dev/null +++ b/native/src/widget/combo_box.rs @@ -0,0 +1,256 @@ +use crate::{ + layer::{self, menu}, + layout, mouse, scrollable, text, Clipboard, Element, Event, Hasher, Layout, + Length, Overlay, Point, Rectangle, Size, Vector, Widget, +}; +use std::borrow::Cow; + +pub struct ComboBox<'a, T, Message> +where + [T]: ToOwned>, +{ + internal: Option>, + options: Cow<'a, [T]>, + selected: Option, + width: Length, + padding: u16, + text_size: Option, +} + +#[derive(Default)] +pub struct State { + menu: menu::State, +} + +pub struct Internal<'a, T, Message> { + menu: &'a mut menu::State, + on_selected: Box Message>, +} + +impl<'a, T: 'a, Message> ComboBox<'a, T, Message> +where + T: ToString, + [T]: ToOwned>, +{ + pub fn new( + state: &'a mut State, + options: impl Into>, + selected: Option, + on_selected: impl Fn(T) -> Message + 'static, + ) -> Self { + Self { + internal: Some(Internal { + menu: &mut state.menu, + on_selected: Box::new(on_selected), + }), + options: options.into(), + selected, + width: Length::Shrink, + text_size: None, + padding: 5, + } + } + + /// Sets the width of the [`ComboBox`]. + /// + /// [`ComboBox`]: struct.Button.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the padding of the [`ComboBox`]. + /// + /// [`ComboBox`]: struct.Button.html + pub fn padding(mut self, padding: u16) -> Self { + self.padding = padding; + self + } + + pub fn text_size(mut self, size: u16) -> Self { + self.text_size = Some(size); + self + } +} + +impl<'a, T: 'a, Message, Renderer> Widget<'a, Message, Renderer> + for ComboBox<'a, T, Message> +where + T: Clone + ToString + Eq, + [T]: ToOwned>, + 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(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, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + if let Some(internal) = &mut self.internal { + if layout.bounds().contains(cursor_position) { + let selected = self.selected.as_ref(); + + internal.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.text_size.unwrap_or(renderer.default_size()), + self.padding, + ) + } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + let is_open = self + .internal + .as_ref() + .map(|internal| internal.menu.is_open()) + .unwrap_or(false); + + if is_open { + if let Some(Internal { menu, on_selected }) = self.internal.take() { + Some(Overlay::new( + layout.position() + + Vector::new(0.0, layout.bounds().height), + Box::new(layer::Menu::new( + menu, + self.options.clone(), + on_selected, + layout.bounds().width.round() as u16, + self.text_size.unwrap_or(20), + self.padding, + )), + )) + } else { + None + } + } else { + None + } + } +} + +pub trait Renderer: text::Renderer + menu::Renderer { + const DEFAULT_PADDING: u16; + + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + selected: Option, + text_size: u16, + padding: u16, + ) -> Self::Output; +} + +impl<'a, T: 'a, Message, Renderer> Into> + for ComboBox<'a, T, Message> +where + T: Clone + ToString + Eq, + [T]: ToOwned>, + Renderer: self::Renderer + 'a, + Message: 'static, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index d7ad98e6..25fd8982 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -113,7 +113,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Scrollable<'a, Message, Renderer> where - Renderer: self::Renderer + column::Renderer, + Renderer: self::Renderer, { fn width(&self) -> Length { Widget::::width(&self.content) @@ -454,7 +454,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 +502,7 @@ pub trait Renderer: crate::Renderer + Sized { impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: 'a + self::Renderer + column::Renderer, + Renderer: 'a + self::Renderer, Message: 'a, { fn from( diff --git a/src/widget.rs b/src/widget.rs index 007bd531..034f02cd 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -19,7 +19,7 @@ #[cfg(not(target_arch = "wasm32"))] mod platform { pub use crate::renderer::widget::{ - button, checkbox, container, pane_grid, progress_bar, radio, + button, checkbox, combo_box, container, pane_grid, progress_bar, radio, scrollable, slider, text_input, Column, Row, Space, Text, }; @@ -44,10 +44,10 @@ mod platform { #[doc(no_inline)] pub use { - button::Button, checkbox::Checkbox, container::Container, image::Image, - pane_grid::PaneGrid, progress_bar::ProgressBar, radio::Radio, - scrollable::Scrollable, slider::Slider, svg::Svg, - text_input::TextInput, + button::Button, checkbox::Checkbox, combo_box::ComboBox, + container::Container, image::Image, pane_grid::PaneGrid, + progress_bar::ProgressBar, radio::Radio, scrollable::Scrollable, + slider::Slider, svg::Svg, text_input::TextInput, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index e51a225c..0186b007 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,7 +20,7 @@ //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index d17b7a5d..0f390c8d 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -11,6 +11,7 @@ use crate::Renderer; pub mod button; pub mod checkbox; +pub mod combo_box; pub mod container; pub mod pane_grid; pub mod progress_bar; @@ -24,6 +25,8 @@ pub use button::Button; #[doc(no_inline)] pub use checkbox::Checkbox; #[doc(no_inline)] +pub use combo_box::ComboBox; +#[doc(no_inline)] pub use container::Container; #[doc(no_inline)] pub use pane_grid::PaneGrid; diff --git a/wgpu/src/widget/combo_box.rs b/wgpu/src/widget/combo_box.rs new file mode 100644 index 00000000..bb3931ef --- /dev/null +++ b/wgpu/src/widget/combo_box.rs @@ -0,0 +1,3 @@ +pub use iced_native::combo_box::State; + +pub type ComboBox<'a, T, Message> = iced_native::ComboBox<'a, T, Message>; -- cgit From f7a370b6b9b5d33d1d3c078d2865eacf813cd652 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 18 Apr 2020 19:27:54 +0200 Subject: Implement `Overlay::translate` --- native/src/overlay.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/native/src/overlay.rs b/native/src/overlay.rs index d7a1e082..57f11fbf 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -1,4 +1,6 @@ -use crate::{layout, Clipboard, Event, Hasher, Layer, Layout, Point, Size}; +use crate::{ + layout, Clipboard, Event, Hasher, Layer, Layout, Point, Size, Vector, +}; use std::rc::Rc; #[allow(missing_debug_implementations)] @@ -18,6 +20,11 @@ where Self { position, layer } } + pub fn translate(mut self, translation: Vector) -> Self { + self.position = self.position + translation; + self + } + pub fn map(self, f: Rc B>) -> Overlay<'a, B, Renderer> where Message: 'static, -- cgit From ada8d7c77f9e1d4a1de762b514c9610f7e09b17f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 18 Apr 2020 19:28:05 +0200 Subject: Implement `Widget::overlay` for `Scrollable` --- examples/combo_box/src/main.rs | 14 ++++++++++---- native/src/widget/scrollable.rs | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs index 75a3f713..977dc196 100644 --- a/examples/combo_box/src/main.rs +++ b/examples/combo_box/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, combo_box, Button, Column, ComboBox, Container, Element, Length, - Sandbox, Settings, Text, + button, combo_box, scrollable, Align, Button, ComboBox, Container, Element, + Length, Sandbox, Scrollable, Settings, Space, Text, }; pub fn main() { @@ -9,6 +9,7 @@ pub fn main() { #[derive(Default)] struct Example { + scroll: scrollable::State, button: button::State, combo_box: combo_box::State, selected_language: Language, @@ -51,8 +52,11 @@ impl Sandbox for Example { let button = Button::new(&mut self.button, Text::new("Press me!")) .on_press(Message::ButtonPressed); - let mut content = Column::new() + let mut content = Scrollable::new(&mut self.scroll) + .width(Length::Fill) + .align_items(Align::Center) .spacing(10) + .push(Space::with_height(Length::Units(800))) .push(Text::new("Which is your favorite language?")) .push(combo_box); @@ -60,7 +64,9 @@ impl Sandbox for Example { content = content.push(Text::new("You are wrong!")); } - content = content.push(button); + content = content + .push(button) + .push(Space::with_height(Length::Units(800))); Container::new(content) .width(Length::Fill) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 25fd8982..92e5265a 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, + Layout, Length, Overlay, Point, Rectangle, Size, Vector, Widget, }; use std::{f32, hash::Hash, u32}; @@ -315,6 +315,22 @@ where self.content.hash_layout(state) } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + 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 = self.state.offset(bounds, content_bounds); + + overlay.translate(Vector::new(0.0, -(offset as f32))) + }) + } } /// The local state of a [`Scrollable`]. -- cgit From e29feef8ba4f95f286039fcc1ca2e53bfe5019c5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 18 Apr 2020 19:53:27 +0200 Subject: Render arrow icon in `ComboBox` --- glow/src/backend.rs | 1 + graphics/fonts/Icons.ttf | Bin 4912 -> 5032 bytes graphics/src/backend.rs | 7 +++++- graphics/src/font.rs | 6 +++++ graphics/src/widget/combo_box.rs | 52 +++++++++++++++++++++++++-------------- native/src/widget/combo_box.rs | 4 ++- wgpu/src/backend.rs | 1 + 7 files changed, 50 insertions(+), 21 deletions(-) diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 8b5b4f9c..e1685816 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -193,6 +193,7 @@ impl iced_graphics::Backend for Backend { impl backend::Text for Backend { const ICON_FONT: Font = font::ICONS; const CHECKMARK_ICON: char = font::CHECKMARK_ICON; + const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON; fn default_size(&self) -> u16 { self.default_text_size diff --git a/graphics/fonts/Icons.ttf b/graphics/fonts/Icons.ttf index 1c832f86..5e455b69 100644 Binary files a/graphics/fonts/Icons.ttf and b/graphics/fonts/Icons.ttf differ diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index b73c636e..dd7dbbc2 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -22,9 +22,14 @@ pub trait Text { /// The `char` representing a ✔ icon in the [`ICON_FONT`]. /// - /// [`ICON_FONT`]: #associatedconst.ICON_FONt + /// [`ICON_FONT`]: #associatedconst.ICON_FONT const CHECKMARK_ICON: char; + /// The `char` representing a ▼ icon in the built-in [`ICONS`] font. + /// + /// [`ICON_FONT`]: #associatedconst.ICON_FONT + const ARROW_DOWN_ICON: char; + /// Returns the default size of text. fn default_size(&self) -> u16; diff --git a/graphics/src/font.rs b/graphics/src/font.rs index bcc28857..5c62681c 100644 --- a/graphics/src/font.rs +++ b/graphics/src/font.rs @@ -31,3 +31,9 @@ pub const ICONS: iced_native::Font = iced_native::Font::External { #[cfg(feature = "font-icons")] #[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))] pub const CHECKMARK_ICON: char = '\u{F00C}'; + +/// The `char` representing a ▼ icon in the built-in [`ICONS`] font. +/// +/// [`ICONS`]: const.ICONS.html +#[cfg(feature = "font-icons")] +pub const ARROW_DOWN_ICON: char = '\u{E800}'; diff --git a/graphics/src/widget/combo_box.rs b/graphics/src/widget/combo_box.rs index 27ea762a..92024c6c 100644 --- a/graphics/src/widget/combo_box.rs +++ b/graphics/src/widget/combo_box.rs @@ -35,27 +35,41 @@ where border_radius: 0, }; + let arrow_down = Primitive::Text { + content: B::ARROW_DOWN_ICON.to_string(), + font: B::ICON_FONT, + size: bounds.height * 0.7, + bounds: Rectangle { + x: bounds.x + bounds.width - f32::from(padding) * 2.0, + y: bounds.center_y(), + ..bounds + }, + color: Color::BLACK, + horizontal_alignment: HorizontalAlignment::Right, + vertical_alignment: VerticalAlignment::Center, + }; + ( - if let Some(label) = selected { - let label = Primitive::Text { - content: label, - size: f32::from(text_size), - font: Font::Default, - color: Color::BLACK, - bounds: Rectangle { - x: bounds.x + f32::from(padding), - y: bounds.center_y(), - ..bounds - }, - horizontal_alignment: HorizontalAlignment::Left, - vertical_alignment: VerticalAlignment::Center, - }; + Primitive::Group { + primitives: if let Some(label) = selected { + let label = Primitive::Text { + content: label, + size: f32::from(text_size), + font: Font::Default, + color: Color::BLACK, + bounds: Rectangle { + x: bounds.x + f32::from(padding), + y: bounds.center_y(), + ..bounds + }, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Center, + }; - Primitive::Group { - primitives: vec![background, label], - } - } else { - background + vec![background, label, arrow_down] + } else { + vec![background, arrow_down] + }, }, if is_mouse_over { mouse::Interaction::Pointer diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index 0b25b836..1b04a9a8 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -126,7 +126,9 @@ where let size = { let intrinsic = Size::new( - max_width as f32 + f32::from(text_size), + max_width as f32 + + f32::from(text_size) + + f32::from(self.padding), f32::from(text_size), ); diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index a25f42f7..c71a6a77 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -248,6 +248,7 @@ impl iced_graphics::Backend for Backend { impl backend::Text for Backend { const ICON_FONT: Font = font::ICONS; const CHECKMARK_ICON: char = font::CHECKMARK_ICON; + const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON; fn default_size(&self) -> u16 { self.default_text_size -- cgit From f655d9b9674fe5a705e26b8797231d93a117395b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 18 Apr 2020 20:44:14 +0200 Subject: Position `Menu` layer based on available space --- examples/combo_box/src/main.rs | 4 ---- native/src/layer/menu.rs | 24 +++++++++++++++++++++--- native/src/widget/combo_box.rs | 10 ++++++---- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs index 977dc196..416e9f76 100644 --- a/examples/combo_box/src/main.rs +++ b/examples/combo_box/src/main.rs @@ -60,10 +60,6 @@ impl Sandbox for Example { .push(Text::new("Which is your favorite language?")) .push(combo_box); - if self.selected_language == Language::Javascript { - content = content.push(Text::new("You are wrong!")); - } - content = content .push(button) .push(Space::with_height(Length::Units(800))); diff --git a/native/src/layer/menu.rs b/native/src/layer/menu.rs index 9e26767b..05026a54 100644 --- a/native/src/layer/menu.rs +++ b/native/src/layer/menu.rs @@ -1,6 +1,7 @@ use crate::{ container, layout, mouse, scrollable, Clipboard, Container, Element, Event, - Hasher, Layer, Layout, Length, Point, Rectangle, Scrollable, Size, Widget, + Hasher, Layer, Layout, Length, Point, Rectangle, Scrollable, Size, Vector, + Widget, }; use std::borrow::Cow; @@ -8,6 +9,7 @@ pub struct Menu<'a, Message, Renderer: self::Renderer> { container: Container<'a, Message, Renderer>, is_open: &'a mut bool, width: u16, + target_height: f32, } #[derive(Default)] @@ -38,6 +40,7 @@ where options: impl Into>, on_selected: Box Message>, width: u16, + target_height: f32, text_size: u16, padding: u16, ) -> Self @@ -60,6 +63,7 @@ where container, is_open: &mut state.is_open, width, + target_height, } } } @@ -75,15 +79,29 @@ where bounds: Size, position: Point, ) -> layout::Node { + let space_below = bounds.height - (position.y + self.target_height); + let space_above = position.y; + let limits = layout::Limits::new( Size::ZERO, - Size::new(bounds.width - position.x, bounds.height - position.y), + Size::new( + bounds.width - position.x, + if space_below > space_above { + space_below + } else { + space_above + }, + ), ) .width(Length::Units(self.width)); let mut node = self.container.layout(renderer, &limits); - node.move_to(position); + node.move_to(if space_below > space_above { + position + Vector::new(0.0, self.target_height) + } else { + position - Vector::new(0.0, node.size().height) + }); node } diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index 1b04a9a8..0f249282 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -1,7 +1,7 @@ use crate::{ layer::{self, menu}, layout, mouse, scrollable, text, Clipboard, Element, Event, Hasher, Layout, - Length, Overlay, Point, Rectangle, Size, Vector, Widget, + Length, Overlay, Point, Rectangle, Size, Widget, }; use std::borrow::Cow; @@ -210,14 +210,16 @@ where if is_open { if let Some(Internal { menu, on_selected }) = self.internal.take() { + let bounds = layout.bounds(); + Some(Overlay::new( - layout.position() - + Vector::new(0.0, layout.bounds().height), + layout.position(), Box::new(layer::Menu::new( menu, self.options.clone(), on_selected, - layout.bounds().width.round() as u16, + bounds.width.round() as u16, + bounds.height, self.text_size.unwrap_or(20), self.padding, )), -- cgit From a2642366240f64f0699fe5fc048b99ace2031751 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 May 2020 01:33:11 +0200 Subject: Implement `Widget::overlay` for `Row` --- native/src/widget/row.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 1ebbac6b..25bd641f 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, Align, Clipboard, Element, Event, Hasher, Layout, Length, Overlay, + Point, Widget, }; use std::u32; @@ -206,6 +206,17 @@ where child.widget.hash_layout(state); } } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + self.children + .iter_mut() + .zip(layout.children()) + .filter_map(|(child, layout)| child.widget.overlay(layout)) + .next() + } } /// The renderer of a [`Row`]. -- cgit From 0ff5a02550e5d5de8fb5fd0643ea424d9e508888 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 23 May 2020 01:07:59 +0200 Subject: Rename `Layer` to `overlay::Content` --- graphics/src/layer.rs | 2 - graphics/src/layer/menu.rs | 102 ------------- graphics/src/lib.rs | 1 + graphics/src/overlay.rs | 1 + graphics/src/overlay/menu.rs | 102 +++++++++++++ native/src/layer.rs | 39 ----- native/src/layer/menu.rs | 323 ----------------------------------------- native/src/lib.rs | 4 +- native/src/overlay.rs | 45 +++--- native/src/overlay/content.rs | 34 +++++ native/src/overlay/menu.rs | 323 +++++++++++++++++++++++++++++++++++++++++ native/src/widget/combo_box.rs | 9 +- 12 files changed, 493 insertions(+), 492 deletions(-) delete mode 100644 graphics/src/layer/menu.rs create mode 100644 graphics/src/overlay.rs create mode 100644 graphics/src/overlay/menu.rs delete mode 100644 native/src/layer.rs delete mode 100644 native/src/layer/menu.rs create mode 100644 native/src/overlay/content.rs create mode 100644 native/src/overlay/menu.rs diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index ddf835a4..6aca738e 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -1,6 +1,4 @@ //! Organize rendering primitives into a flattened list of layers. -mod menu; - use crate::image; use crate::svg; use crate::triangle; diff --git a/graphics/src/layer/menu.rs b/graphics/src/layer/menu.rs deleted file mode 100644 index e94ef964..00000000 --- a/graphics/src/layer/menu.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::backend::Backend; -use crate::{Primitive, Renderer}; -use iced_native::{ - layer, mouse, Background, Color, Font, HorizontalAlignment, Point, - Rectangle, VerticalAlignment, -}; - -impl layer::menu::Renderer for Renderer -where - B: Backend, -{ - fn decorate( - &mut self, - bounds: Rectangle, - _cursor_position: Point, - (primitives, mouse_cursor): Self::Output, - ) -> Self::Output { - ( - Primitive::Group { - primitives: vec![ - Primitive::Quad { - bounds, - background: Background::Color( - [0.87, 0.87, 0.87].into(), - ), - border_color: [0.7, 0.7, 0.7].into(), - border_width: 1, - border_radius: 0, - }, - primitives, - ], - }, - mouse_cursor, - ) - } - - fn draw( - &mut self, - bounds: Rectangle, - cursor_position: Point, - options: &[T], - hovered_option: Option, - text_size: u16, - padding: u16, - ) -> Self::Output { - use std::f32; - - let is_mouse_over = bounds.contains(cursor_position); - - let mut primitives = Vec::new(); - - for (i, option) in options.iter().enumerate() { - let is_selected = hovered_option == Some(i); - - let bounds = Rectangle { - x: bounds.x, - y: bounds.y - + ((text_size as usize + padding as usize * 2) * i) as f32, - width: bounds.width, - height: f32::from(text_size + padding * 2), - }; - - if is_selected { - primitives.push(Primitive::Quad { - bounds, - background: Background::Color([0.4, 0.4, 1.0].into()), - border_color: Color::TRANSPARENT, - border_width: 0, - border_radius: 0, - }); - } - - primitives.push(Primitive::Text { - content: option.to_string(), - bounds: Rectangle { - x: bounds.x + f32::from(padding), - y: bounds.center_y(), - width: f32::INFINITY, - ..bounds - }, - size: f32::from(text_size), - font: Font::Default, - color: if is_selected { - Color::WHITE - } else { - Color::BLACK - }, - horizontal_alignment: HorizontalAlignment::Left, - vertical_alignment: VerticalAlignment::Center, - }); - } - - ( - Primitive::Group { primitives }, - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index bad35ccf..92e8432e 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,6 +9,7 @@ #![forbid(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] mod antialiasing; +mod overlay; mod primitive; mod renderer; mod transformation; diff --git a/graphics/src/overlay.rs b/graphics/src/overlay.rs new file mode 100644 index 00000000..c57668d4 --- /dev/null +++ b/graphics/src/overlay.rs @@ -0,0 +1 @@ +mod menu; diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs new file mode 100644 index 00000000..f4204f25 --- /dev/null +++ b/graphics/src/overlay/menu.rs @@ -0,0 +1,102 @@ +use crate::backend::Backend; +use crate::{Primitive, Renderer}; +use iced_native::{ + mouse, overlay, Background, Color, Font, HorizontalAlignment, Point, + Rectangle, VerticalAlignment, +}; + +impl overlay::menu::Renderer for Renderer +where + B: Backend, +{ + fn decorate( + &mut self, + bounds: Rectangle, + _cursor_position: Point, + (primitives, mouse_cursor): Self::Output, + ) -> Self::Output { + ( + Primitive::Group { + primitives: vec![ + Primitive::Quad { + bounds, + background: Background::Color( + [0.87, 0.87, 0.87].into(), + ), + border_color: [0.7, 0.7, 0.7].into(), + border_width: 1, + border_radius: 0, + }, + primitives, + ], + }, + mouse_cursor, + ) + } + + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + options: &[T], + hovered_option: Option, + text_size: u16, + padding: u16, + ) -> Self::Output { + use std::f32; + + let is_mouse_over = bounds.contains(cursor_position); + + let mut primitives = Vec::new(); + + for (i, option) in options.iter().enumerate() { + let is_selected = hovered_option == Some(i); + + let bounds = Rectangle { + x: bounds.x, + y: bounds.y + + ((text_size as usize + padding as usize * 2) * i) as f32, + width: bounds.width, + height: f32::from(text_size + padding * 2), + }; + + if is_selected { + primitives.push(Primitive::Quad { + bounds, + background: Background::Color([0.4, 0.4, 1.0].into()), + border_color: Color::TRANSPARENT, + border_width: 0, + border_radius: 0, + }); + } + + primitives.push(Primitive::Text { + content: option.to_string(), + bounds: Rectangle { + x: bounds.x + f32::from(padding), + y: bounds.center_y(), + width: f32::INFINITY, + ..bounds + }, + size: f32::from(text_size), + font: Font::Default, + color: if is_selected { + Color::WHITE + } else { + Color::BLACK + }, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Center, + }); + } + + ( + Primitive::Group { primitives }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/native/src/layer.rs b/native/src/layer.rs deleted file mode 100644 index eacfe94b..00000000 --- a/native/src/layer.rs +++ /dev/null @@ -1,39 +0,0 @@ -pub mod menu; - -#[doc(no_inline)] -pub use menu::Menu; - -use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size}; - -pub trait Layer -where - Renderer: crate::Renderer, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node; - - fn draw( - &self, - renderer: &mut Renderer, - defaults: &Renderer::Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> Renderer::Output; - - fn hash_layout(&self, state: &mut Hasher, position: Point); - - fn on_event( - &mut self, - _event: Event, - _layout: Layout<'_>, - _cursor_position: Point, - _messages: &mut Vec, - _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, - ) { - } -} diff --git a/native/src/layer/menu.rs b/native/src/layer/menu.rs deleted file mode 100644 index 05026a54..00000000 --- a/native/src/layer/menu.rs +++ /dev/null @@ -1,323 +0,0 @@ -use crate::{ - container, layout, mouse, scrollable, Clipboard, Container, Element, Event, - Hasher, Layer, Layout, Length, Point, Rectangle, Scrollable, Size, Vector, - Widget, -}; -use std::borrow::Cow; - -pub struct Menu<'a, Message, Renderer: self::Renderer> { - container: Container<'a, Message, Renderer>, - is_open: &'a mut bool, - width: u16, - target_height: f32, -} - -#[derive(Default)] -pub struct State { - scrollable: scrollable::State, - hovered_option: Option, - is_open: bool, -} - -impl State { - pub fn is_open(&self) -> bool { - self.is_open - } - - pub fn open(&mut self, hovered_option: Option) { - self.is_open = true; - self.hovered_option = hovered_option; - } -} - -impl<'a, Message, Renderer: self::Renderer> Menu<'a, Message, Renderer> -where - Message: 'static, - Renderer: 'a, -{ - pub fn new( - state: &'a mut State, - options: impl Into>, - on_selected: Box Message>, - width: u16, - target_height: f32, - text_size: u16, - padding: u16, - ) -> Self - where - T: Clone + ToString, - [T]: ToOwned>, - { - let container = Container::new( - Scrollable::new(&mut state.scrollable).push(List::new( - &mut state.hovered_option, - options, - on_selected, - text_size, - padding, - )), - ) - .padding(1); - - Self { - container, - is_open: &mut state.is_open, - width, - target_height, - } - } -} - -impl<'a, Message, Renderer> Layer - for Menu<'a, Message, Renderer> -where - Renderer: self::Renderer, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node { - let space_below = bounds.height - (position.y + self.target_height); - let space_above = position.y; - - let limits = layout::Limits::new( - Size::ZERO, - Size::new( - bounds.width - position.x, - if space_below > space_above { - space_below - } else { - space_above - }, - ), - ) - .width(Length::Units(self.width)); - - let mut node = self.container.layout(renderer, &limits); - - node.move_to(if space_below > space_above { - position + Vector::new(0.0, self.target_height) - } else { - position - Vector::new(0.0, node.size().height) - }); - - node - } - - fn hash_layout(&self, state: &mut Hasher, position: Point) { - use std::hash::Hash; - - (position.x as u32).hash(state); - (position.y as u32).hash(state); - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, - ) { - let bounds = layout.bounds(); - let current_messages = messages.len(); - - self.container.on_event( - event.clone(), - layout, - cursor_position, - messages, - renderer, - clipboard, - ); - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - if !bounds.contains(cursor_position) - || current_messages < messages.len() => - { - *self.is_open = false; - } - _ => {} - } - } - - fn draw( - &self, - renderer: &mut Renderer, - defaults: &Renderer::Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> Renderer::Output { - let primitives = - self.container - .draw(renderer, defaults, layout, cursor_position); - - renderer.decorate(layout.bounds(), cursor_position, primitives) - } -} - -struct List<'a, T, Message> -where - [T]: ToOwned, -{ - hovered_option: &'a mut Option, - options: Cow<'a, [T]>, - on_selected: Box Message>, - text_size: u16, - padding: u16, -} - -impl<'a, T, Message> List<'a, T, Message> -where - [T]: ToOwned, -{ - pub fn new( - hovered_option: &'a mut Option, - options: impl Into>, - on_selected: Box Message>, - text_size: u16, - padding: u16, - ) -> Self { - List { - hovered_option, - options: options.into(), - on_selected, - text_size, - padding, - } - } -} - -impl<'a, T, Message, Renderer> Widget<'a, Message, Renderer> - for List<'a, T, Message> -where - T: ToString + Clone, - [T]: ToOwned, - Renderer: self::Renderer, -{ - fn width(&self) -> Length { - Length::Fill - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - use std::f32; - - let limits = limits.width(Length::Fill).height(Length::Shrink); - - let size = { - let intrinsic = Size::new( - 0.0, - f32::from(self.text_size + self.padding * 2) - * self.options.len() as f32, - ); - - limits.resolve(intrinsic) - }; - - layout::Node::new(size) - } - - fn hash_layout(&self, state: &mut Hasher) { - use std::hash::Hash as _; - - 0.hash(state); - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, - ) { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - if let Some(index) = *self.hovered_option { - if let Some(option) = self.options.get(index) { - messages.push((self.on_selected)(option.clone())); - } - } - } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - *self.hovered_option = Some( - ((cursor_position.y - bounds.y) - / f32::from(self.text_size + self.padding * 2)) - as usize, - ); - } - } - _ => {} - } - } - - 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.options, - *self.hovered_option, - self.text_size, - self.padding, - ) - } -} - -pub trait Renderer: scrollable::Renderer + container::Renderer { - fn decorate( - &mut self, - bounds: Rectangle, - cursor_position: Point, - primitive: Self::Output, - ) -> Self::Output; - - fn draw( - &mut self, - bounds: Rectangle, - cursor_position: Point, - options: &[T], - hovered_option: Option, - text_size: u16, - padding: u16, - ) -> Self::Output; -} - -impl<'a, T, Message, Renderer> Into> - for List<'a, T, Message> -where - T: ToString + Clone, - [T]: ToOwned, - Message: 'static, - Renderer: self::Renderer, -{ - fn into(self) -> Element<'a, Message, Renderer> { - Element::new(self) - } -} diff --git a/native/src/lib.rs b/native/src/lib.rs index 99d80126..ea328592 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -36,9 +36,9 @@ #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] pub mod keyboard; -pub mod layer; pub mod layout; pub mod mouse; +pub mod overlay; pub mod program; pub mod renderer; pub mod subscription; @@ -49,7 +49,6 @@ mod clipboard; mod element; mod event; mod hasher; -mod overlay; mod runtime; mod user_interface; @@ -76,7 +75,6 @@ pub use debug::Debug; pub use element::Element; pub use event::Event; pub use hasher::Hasher; -pub use layer::Layer; pub use layout::Layout; pub use overlay::Overlay; pub use program::Program; diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 57f11fbf..96390348 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -1,12 +1,17 @@ -use crate::{ - layout, Clipboard, Event, Hasher, Layer, Layout, Point, Size, Vector, -}; +mod content; + +pub mod menu; + +pub use content::Content; +pub use menu::Menu; + +use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size, Vector}; use std::rc::Rc; #[allow(missing_debug_implementations)] pub struct Overlay<'a, Message, Renderer> { position: Point, - layer: Box + 'a>, + content: Box + 'a>, } impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> @@ -15,9 +20,9 @@ where { pub fn new( position: Point, - layer: Box + 'a>, + content: Box + 'a>, ) -> Self { - Self { position, layer } + Self { position, content } } pub fn translate(mut self, translation: Vector) -> Self { @@ -33,12 +38,12 @@ where { Overlay { position: self.position, - layer: Box::new(Map::new(self.layer, f)), + content: Box::new(Map::new(self.content, f)), } } pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node { - self.layer.layout(renderer, bounds, self.position) + self.content.layout(renderer, bounds, self.position) } pub fn draw( @@ -48,11 +53,12 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - self.layer.draw(renderer, defaults, layout, cursor_position) + self.content + .draw(renderer, defaults, layout, cursor_position) } pub fn hash_layout(&self, state: &mut Hasher) { - self.layer.hash_layout(state, self.position); + self.content.hash_layout(state, self.position); } pub fn on_event( @@ -64,7 +70,7 @@ where renderer: &Renderer, clipboard: Option<&dyn Clipboard>, ) { - self.layer.on_event( + self.content.on_event( event, layout, cursor_position, @@ -76,20 +82,20 @@ where } struct Map<'a, A, B, Renderer> { - layer: Box + 'a>, + content: Box + 'a>, mapper: Rc B>, } impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { pub fn new( - layer: Box + 'a>, + content: Box + 'a>, mapper: Rc B + 'static>, ) -> Map<'a, A, B, Renderer> { - Map { layer, mapper } + Map { content, mapper } } } -impl<'a, A, B, Renderer> Layer for Map<'a, A, B, Renderer> +impl<'a, A, B, Renderer> Content for Map<'a, A, B, Renderer> where Renderer: crate::Renderer, { @@ -99,7 +105,7 @@ where bounds: Size, position: Point, ) -> layout::Node { - self.layer.layout(renderer, bounds, position) + self.content.layout(renderer, bounds, position) } fn on_event( @@ -113,7 +119,7 @@ where ) { let mut original_messages = Vec::new(); - self.layer.on_event( + self.content.on_event( event, layout, cursor_position, @@ -134,10 +140,11 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - self.layer.draw(renderer, defaults, layout, cursor_position) + self.content + .draw(renderer, defaults, layout, cursor_position) } fn hash_layout(&self, state: &mut Hasher, position: Point) { - self.layer.hash_layout(state, position); + self.content.hash_layout(state, position); } } diff --git a/native/src/overlay/content.rs b/native/src/overlay/content.rs new file mode 100644 index 00000000..5259c4b8 --- /dev/null +++ b/native/src/overlay/content.rs @@ -0,0 +1,34 @@ +use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size}; + +pub trait Content +where + Renderer: crate::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node; + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output; + + fn hash_layout(&self, state: &mut Hasher, position: Point); + + fn on_event( + &mut self, + _event: Event, + _layout: Layout<'_>, + _cursor_position: Point, + _messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + } +} diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs new file mode 100644 index 00000000..05c41181 --- /dev/null +++ b/native/src/overlay/menu.rs @@ -0,0 +1,323 @@ +use crate::{ + container, layout, mouse, overlay, scrollable, Clipboard, Container, + Element, Event, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size, + Vector, Widget, +}; +use std::borrow::Cow; + +pub struct Menu<'a, Message, Renderer: self::Renderer> { + container: Container<'a, Message, Renderer>, + is_open: &'a mut bool, + width: u16, + target_height: f32, +} + +#[derive(Default)] +pub struct State { + scrollable: scrollable::State, + hovered_option: Option, + is_open: bool, +} + +impl State { + pub fn is_open(&self) -> bool { + self.is_open + } + + pub fn open(&mut self, hovered_option: Option) { + self.is_open = true; + self.hovered_option = hovered_option; + } +} + +impl<'a, Message, Renderer: self::Renderer> Menu<'a, Message, Renderer> +where + Message: 'static, + Renderer: 'a, +{ + pub fn new( + state: &'a mut State, + options: impl Into>, + on_selected: Box Message>, + width: u16, + target_height: f32, + text_size: u16, + padding: u16, + ) -> Self + where + T: Clone + ToString, + [T]: ToOwned>, + { + let container = Container::new( + Scrollable::new(&mut state.scrollable).push(List::new( + &mut state.hovered_option, + options, + on_selected, + text_size, + padding, + )), + ) + .padding(1); + + Self { + container, + is_open: &mut state.is_open, + width, + target_height, + } + } +} + +impl<'a, Message, Renderer> overlay::Content + for Menu<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + let space_below = bounds.height - (position.y + self.target_height); + let space_above = position.y; + + let limits = layout::Limits::new( + Size::ZERO, + Size::new( + bounds.width - position.x, + if space_below > space_above { + space_below + } else { + space_above + }, + ), + ) + .width(Length::Units(self.width)); + + let mut node = self.container.layout(renderer, &limits); + + node.move_to(if space_below > space_above { + position + Vector::new(0.0, self.target_height) + } else { + position - Vector::new(0.0, node.size().height) + }); + + node + } + + fn hash_layout(&self, state: &mut Hasher, position: Point) { + use std::hash::Hash; + + (position.x as u32).hash(state); + (position.y as u32).hash(state); + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, + ) { + let bounds = layout.bounds(); + let current_messages = messages.len(); + + self.container.on_event( + event.clone(), + layout, + cursor_position, + messages, + renderer, + clipboard, + ); + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + if !bounds.contains(cursor_position) + || current_messages < messages.len() => + { + *self.is_open = false; + } + _ => {} + } + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + let primitives = + self.container + .draw(renderer, defaults, layout, cursor_position); + + renderer.decorate(layout.bounds(), cursor_position, primitives) + } +} + +struct List<'a, T, Message> +where + [T]: ToOwned, +{ + hovered_option: &'a mut Option, + options: Cow<'a, [T]>, + on_selected: Box Message>, + text_size: u16, + padding: u16, +} + +impl<'a, T, Message> List<'a, T, Message> +where + [T]: ToOwned, +{ + pub fn new( + hovered_option: &'a mut Option, + options: impl Into>, + on_selected: Box Message>, + text_size: u16, + padding: u16, + ) -> Self { + List { + hovered_option, + options: options.into(), + on_selected, + text_size, + padding, + } + } +} + +impl<'a, T, Message, Renderer> Widget<'a, Message, Renderer> + for List<'a, T, Message> +where + T: ToString + Clone, + [T]: ToOwned, + Renderer: self::Renderer, +{ + fn width(&self) -> Length { + Length::Fill + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + use std::f32; + + let limits = limits.width(Length::Fill).height(Length::Shrink); + + let size = { + let intrinsic = Size::new( + 0.0, + f32::from(self.text_size + self.padding * 2) + * self.options.len() as f32, + ); + + limits.resolve(intrinsic) + }; + + layout::Node::new(size) + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash as _; + + 0.hash(state); + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + if let Some(index) = *self.hovered_option { + if let Some(option) = self.options.get(index) { + messages.push((self.on_selected)(option.clone())); + } + } + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + *self.hovered_option = Some( + ((cursor_position.y - bounds.y) + / f32::from(self.text_size + self.padding * 2)) + as usize, + ); + } + } + _ => {} + } + } + + 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.options, + *self.hovered_option, + self.text_size, + self.padding, + ) + } +} + +pub trait Renderer: scrollable::Renderer + container::Renderer { + fn decorate( + &mut self, + bounds: Rectangle, + cursor_position: Point, + primitive: Self::Output, + ) -> Self::Output; + + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + options: &[T], + hovered_option: Option, + text_size: u16, + padding: u16, + ) -> Self::Output; +} + +impl<'a, T, Message, Renderer> Into> + for List<'a, T, Message> +where + T: ToString + Clone, + [T]: ToOwned, + Message: 'static, + Renderer: self::Renderer, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index 0f249282..2adee884 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -1,7 +1,8 @@ use crate::{ - layer::{self, menu}, - layout, mouse, scrollable, text, Clipboard, Element, Event, Hasher, Layout, - Length, Overlay, Point, Rectangle, Size, Widget, + layout, mouse, + overlay::menu::{self, Menu}, + scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, + Overlay, Point, Rectangle, Size, Widget, }; use std::borrow::Cow; @@ -214,7 +215,7 @@ where Some(Overlay::new( layout.position(), - Box::new(layer::Menu::new( + Box::new(Menu::new( menu, self.options.clone(), on_selected, -- cgit From 61f22b1db23f3495145a9a4f7255311fe8381998 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 Jun 2020 20:41:11 +0200 Subject: Add styling support for `ComboBox` and `Menu` --- glow/src/widget/combo_box.rs | 7 +++- graphics/src/lib.rs | 4 +-- graphics/src/overlay.rs | 2 +- graphics/src/overlay/menu.rs | 24 ++++++++------ graphics/src/widget/combo_box.rs | 42 ++++++++++++++++-------- native/src/overlay/menu.rs | 31 ++++++++++++++---- native/src/widget/combo_box.rs | 33 ++++++++++++++++--- style/src/combo_box.rs | 70 ++++++++++++++++++++++++++++++++++++++++ style/src/lib.rs | 2 ++ style/src/menu.rs | 25 ++++++++++++++ wgpu/src/widget/combo_box.rs | 7 +++- 11 files changed, 206 insertions(+), 41 deletions(-) create mode 100644 style/src/combo_box.rs create mode 100644 style/src/menu.rs diff --git a/glow/src/widget/combo_box.rs b/glow/src/widget/combo_box.rs index bb3931ef..bfface29 100644 --- a/glow/src/widget/combo_box.rs +++ b/glow/src/widget/combo_box.rs @@ -1,3 +1,8 @@ pub use iced_native::combo_box::State; -pub type ComboBox<'a, T, Message> = iced_native::ComboBox<'a, T, Message>; +pub use iced_graphics::combo_box::{Style, StyleSheet}; +pub use iced_graphics::overlay::menu::Style as Menu; + +/// A widget allowing the selection of a single value from a list of options. +pub type ComboBox<'a, T, Message> = + iced_native::ComboBox<'a, T, Message, crate::Renderer>; diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 92e8432e..0c427634 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,18 +9,18 @@ #![forbid(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] mod antialiasing; -mod overlay; mod primitive; mod renderer; mod transformation; mod viewport; -mod widget; pub mod backend; pub mod defaults; pub mod font; pub mod layer; +pub mod overlay; pub mod triangle; +pub mod widget; pub mod window; #[doc(no_inline)] diff --git a/graphics/src/overlay.rs b/graphics/src/overlay.rs index c57668d4..b9a0e3e0 100644 --- a/graphics/src/overlay.rs +++ b/graphics/src/overlay.rs @@ -1 +1 @@ -mod menu; +pub mod menu; diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index f4204f25..13065645 100644 --- a/graphics/src/overlay/menu.rs +++ b/graphics/src/overlay/menu.rs @@ -1,18 +1,23 @@ use crate::backend::Backend; use crate::{Primitive, Renderer}; use iced_native::{ - mouse, overlay, Background, Color, Font, HorizontalAlignment, Point, - Rectangle, VerticalAlignment, + mouse, overlay, Color, Font, HorizontalAlignment, Point, Rectangle, + VerticalAlignment, }; +pub use iced_style::menu::Style; + impl overlay::menu::Renderer for Renderer where B: Backend, { + type Style = Style; + fn decorate( &mut self, bounds: Rectangle, _cursor_position: Point, + style: &Style, (primitives, mouse_cursor): Self::Output, ) -> Self::Output { ( @@ -20,11 +25,9 @@ where primitives: vec![ Primitive::Quad { bounds, - background: Background::Color( - [0.87, 0.87, 0.87].into(), - ), - border_color: [0.7, 0.7, 0.7].into(), - border_width: 1, + background: style.background, + border_color: style.border_color, + border_width: style.border_width, border_radius: 0, }, primitives, @@ -42,6 +45,7 @@ where hovered_option: Option, text_size: u16, padding: u16, + style: &Style, ) -> Self::Output { use std::f32; @@ -63,7 +67,7 @@ where if is_selected { primitives.push(Primitive::Quad { bounds, - background: Background::Color([0.4, 0.4, 1.0].into()), + background: style.selected_background, border_color: Color::TRANSPARENT, border_width: 0, border_radius: 0, @@ -81,9 +85,9 @@ where size: f32::from(text_size), font: Font::Default, color: if is_selected { - Color::WHITE + style.selected_text_color } else { - Color::BLACK + style.text_color }, horizontal_alignment: HorizontalAlignment::Left, vertical_alignment: VerticalAlignment::Center, diff --git a/graphics/src/widget/combo_box.rs b/graphics/src/widget/combo_box.rs index 92024c6c..078b5def 100644 --- a/graphics/src/widget/combo_box.rs +++ b/graphics/src/widget/combo_box.rs @@ -1,18 +1,29 @@ use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{ - mouse, Background, Color, Font, HorizontalAlignment, Point, Rectangle, - VerticalAlignment, + mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment, }; +use iced_style::menu; -pub use iced_native::ComboBox; +pub use iced_native::combo_box::State; +pub use iced_style::combo_box::{Style, StyleSheet}; + +/// A widget allowing the selection of a single value from a list of options. +pub type ComboBox<'a, T, Message, Backend> = + iced_native::ComboBox<'a, T, Message, Renderer>; impl iced_native::combo_box::Renderer for Renderer where B: Backend + backend::Text, { + type Style = Box; + const DEFAULT_PADDING: u16 = 5; + fn menu_style(style: &Box) -> menu::Style { + style.menu() + } + fn draw( &mut self, bounds: Rectangle, @@ -20,31 +31,34 @@ where selected: Option, text_size: u16, padding: u16, + style: &Box, ) -> Self::Output { let is_mouse_over = bounds.contains(cursor_position); + let style = if is_mouse_over { + style.hovered() + } else { + style.active() + }; + let background = Primitive::Quad { bounds, - background: Background::Color([0.87, 0.87, 0.87].into()), - border_color: if is_mouse_over { - Color::BLACK - } else { - [0.7, 0.7, 0.7].into() - }, - border_width: 1, - border_radius: 0, + background: style.background, + border_color: style.border_color, + border_width: style.border_width, + border_radius: style.border_radius, }; let arrow_down = Primitive::Text { content: B::ARROW_DOWN_ICON.to_string(), font: B::ICON_FONT, - size: bounds.height * 0.7, + size: bounds.height * style.icon_size, bounds: Rectangle { x: bounds.x + bounds.width - f32::from(padding) * 2.0, y: bounds.center_y(), ..bounds }, - color: Color::BLACK, + color: style.text_color, horizontal_alignment: HorizontalAlignment::Right, vertical_alignment: VerticalAlignment::Center, }; @@ -56,7 +70,7 @@ where content: label, size: f32::from(text_size), font: Font::Default, - color: Color::BLACK, + color: style.text_color, bounds: Rectangle { x: bounds.x + f32::from(padding), y: bounds.center_y(), diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 05c41181..9c180671 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -10,6 +10,7 @@ pub struct Menu<'a, Message, Renderer: self::Renderer> { is_open: &'a mut bool, width: u16, target_height: f32, + style: ::Style, } #[derive(Default)] @@ -43,6 +44,7 @@ where target_height: f32, text_size: u16, padding: u16, + style: ::Style, ) -> Self where T: Clone + ToString, @@ -55,6 +57,7 @@ where on_selected, text_size, padding, + style.clone(), )), ) .padding(1); @@ -64,6 +67,7 @@ where is_open: &mut state.is_open, width, target_height, + style, } } } @@ -156,11 +160,16 @@ where self.container .draw(renderer, defaults, layout, cursor_position); - renderer.decorate(layout.bounds(), cursor_position, primitives) + renderer.decorate( + layout.bounds(), + cursor_position, + &self.style, + primitives, + ) } } -struct List<'a, T, Message> +struct List<'a, T, Message, Renderer: self::Renderer> where [T]: ToOwned, { @@ -169,9 +178,10 @@ where on_selected: Box Message>, text_size: u16, padding: u16, + style: ::Style, } -impl<'a, T, Message> List<'a, T, Message> +impl<'a, T, Message, Renderer: self::Renderer> List<'a, T, Message, Renderer> where [T]: ToOwned, { @@ -181,6 +191,7 @@ where on_selected: Box Message>, text_size: u16, padding: u16, + style: ::Style, ) -> Self { List { hovered_option, @@ -188,12 +199,13 @@ where on_selected, text_size, padding, + style, } } } -impl<'a, T, Message, Renderer> Widget<'a, Message, Renderer> - for List<'a, T, Message> +impl<'a, T, Message, Renderer: self::Renderer> Widget<'a, Message, Renderer> + for List<'a, T, Message, Renderer> where T: ToString + Clone, [T]: ToOwned, @@ -286,15 +298,19 @@ where *self.hovered_option, self.text_size, self.padding, + &self.style, ) } } pub trait Renderer: scrollable::Renderer + container::Renderer { + type Style: Default + Clone; + fn decorate( &mut self, bounds: Rectangle, cursor_position: Point, + style: &::Style, primitive: Self::Output, ) -> Self::Output; @@ -306,16 +322,17 @@ pub trait Renderer: scrollable::Renderer + container::Renderer { hovered_option: Option, text_size: u16, padding: u16, + style: &::Style, ) -> Self::Output; } impl<'a, T, Message, Renderer> Into> - for List<'a, T, Message> + for List<'a, T, Message, Renderer> where T: ToString + Clone, [T]: ToOwned, Message: 'static, - Renderer: self::Renderer, + Renderer: 'a + self::Renderer, { fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index 2adee884..df2a530a 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::borrow::Cow; -pub struct ComboBox<'a, T, Message> +pub struct ComboBox<'a, T, Message, Renderer: self::Renderer> where [T]: ToOwned>, { @@ -16,6 +16,7 @@ where width: Length, padding: u16, text_size: Option, + style: ::Style, } #[derive(Default)] @@ -28,7 +29,8 @@ pub struct Internal<'a, T, Message> { on_selected: Box Message>, } -impl<'a, T: 'a, Message> ComboBox<'a, T, Message> +impl<'a, T: 'a, Message, Renderer: self::Renderer> + ComboBox<'a, T, Message, Renderer> where T: ToString, [T]: ToOwned>, @@ -48,7 +50,8 @@ where selected, width: Length::Shrink, text_size: None, - padding: 5, + padding: Renderer::DEFAULT_PADDING, + style: ::Style::default(), } } @@ -72,10 +75,21 @@ where self.text_size = Some(size); self } + + /// Sets the style of the [`ComboBox`]. + /// + /// [`ComboBox`]: struct.ComboBox.html + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { + self.style = style.into(); + self + } } impl<'a, T: 'a, Message, Renderer> Widget<'a, Message, Renderer> - for ComboBox<'a, T, Message> + for ComboBox<'a, T, Message, Renderer> where T: Clone + ToString + Eq, [T]: ToOwned>, @@ -196,6 +210,7 @@ where self.selected.as_ref().map(ToString::to_string), self.text_size.unwrap_or(renderer.default_size()), self.padding, + &self.style, ) } @@ -223,6 +238,7 @@ where bounds.height, self.text_size.unwrap_or(20), self.padding, + Renderer::menu_style(&self.style), )), )) } else { @@ -235,8 +251,14 @@ where } pub trait Renderer: text::Renderer + menu::Renderer { + type Style: Default; + const DEFAULT_PADDING: u16; + fn menu_style( + style: &::Style, + ) -> ::Style; + fn draw( &mut self, bounds: Rectangle, @@ -244,11 +266,12 @@ pub trait Renderer: text::Renderer + menu::Renderer { selected: Option, text_size: u16, padding: u16, + style: &::Style, ) -> Self::Output; } impl<'a, T: 'a, Message, Renderer> Into> - for ComboBox<'a, T, Message> + for ComboBox<'a, T, Message, Renderer> where T: Clone + ToString + Eq, [T]: ToOwned>, diff --git a/style/src/combo_box.rs b/style/src/combo_box.rs new file mode 100644 index 00000000..4d0c4e46 --- /dev/null +++ b/style/src/combo_box.rs @@ -0,0 +1,70 @@ +use crate::menu; +use iced_core::{Background, Color}; + +/// The appearance of a combo box. +#[derive(Debug, Clone, Copy)] +pub struct Style { + pub text_color: Color, + pub background: Background, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, + pub icon_size: f32, +} + +impl std::default::Default for Style { + fn default() -> Self { + Self { + text_color: Color::BLACK, + background: Background::Color([0.87, 0.87, 0.87].into()), + border_radius: 0, + border_width: 1, + border_color: [0.7, 0.7, 0.7].into(), + icon_size: 0.7, + } + } +} + +/// A set of rules that dictate the style of a container. +pub trait StyleSheet { + fn menu(&self) -> menu::Style; + + fn active(&self) -> Style; + + /// Produces the style of a container. + fn hovered(&self) -> Style; +} + +struct Default; + +impl StyleSheet for Default { + fn menu(&self) -> menu::Style { + menu::Style::default() + } + + fn active(&self) -> Style { + Style::default() + } + + fn hovered(&self) -> Style { + Style { + border_color: Color::BLACK, + ..self.active() + } + } +} + +impl std::default::Default for Box { + fn default() -> Self { + Box::new(Default) + } +} + +impl From for Box +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/style/src/lib.rs b/style/src/lib.rs index 72d83aec..b19d6600 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -6,7 +6,9 @@ pub use iced_core::{Background, Color}; pub mod button; pub mod checkbox; +pub mod combo_box; pub mod container; +pub mod menu; pub mod progress_bar; pub mod radio; pub mod scrollable; diff --git a/style/src/menu.rs b/style/src/menu.rs new file mode 100644 index 00000000..e8321dc7 --- /dev/null +++ b/style/src/menu.rs @@ -0,0 +1,25 @@ +use iced_core::{Background, Color}; + +/// The appearance of a menu. +#[derive(Debug, Clone, Copy)] +pub struct Style { + pub text_color: Color, + pub background: Background, + pub border_width: u16, + pub border_color: Color, + pub selected_text_color: Color, + pub selected_background: Background, +} + +impl std::default::Default for Style { + fn default() -> Self { + Self { + text_color: Color::BLACK, + background: Background::Color([0.87, 0.87, 0.87].into()), + border_width: 1, + border_color: [0.7, 0.7, 0.7].into(), + selected_text_color: Color::WHITE, + selected_background: Background::Color([0.4, 0.4, 1.0].into()), + } + } +} diff --git a/wgpu/src/widget/combo_box.rs b/wgpu/src/widget/combo_box.rs index bb3931ef..bfface29 100644 --- a/wgpu/src/widget/combo_box.rs +++ b/wgpu/src/widget/combo_box.rs @@ -1,3 +1,8 @@ pub use iced_native::combo_box::State; -pub type ComboBox<'a, T, Message> = iced_native::ComboBox<'a, T, Message>; +pub use iced_graphics::combo_box::{Style, StyleSheet}; +pub use iced_graphics::overlay::menu::Style as Menu; + +/// A widget allowing the selection of a single value from a list of options. +pub type ComboBox<'a, T, Message> = + iced_native::ComboBox<'a, T, Message, crate::Renderer>; -- cgit From 625979b6652a8a14a0eaf6bd62f1e9a8da0ae421 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Jul 2020 05:44:10 +0200 Subject: Draft `Widget::overlay` idempotency --- graphics/src/overlay/menu.rs | 4 +- native/src/element.rs | 14 +++-- native/src/overlay/menu.rs | 31 ++++++---- native/src/program/state.rs | 4 +- native/src/user_interface.rs | 128 +++++++++++++++++++++------------------- native/src/widget.rs | 6 +- native/src/widget/column.rs | 2 +- native/src/widget/combo_box.rs | 68 +++++++++------------ native/src/widget/container.rs | 2 +- native/src/widget/row.rs | 2 +- native/src/widget/scrollable.rs | 8 ++- 11 files changed, 136 insertions(+), 133 deletions(-) diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index 13065645..914baa6a 100644 --- a/graphics/src/overlay/menu.rs +++ b/graphics/src/overlay/menu.rs @@ -1,4 +1,4 @@ -use crate::backend::Backend; +use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{ mouse, overlay, Color, Font, HorizontalAlignment, Point, Rectangle, @@ -9,7 +9,7 @@ pub use iced_style::menu::Style; impl overlay::menu::Renderer for Renderer where - B: Backend, + B: Backend + backend::Text, { type Style = Style; diff --git a/native/src/element.rs b/native/src/element.rs index c881871a..b00f9e55 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -273,10 +273,10 @@ where self.widget.hash_layout(state); } - pub fn overlay( - &mut self, + pub fn overlay<'b>( + &'b mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { self.widget.overlay(layout) } } @@ -366,10 +366,12 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { + let mapper = self.mapper.clone(); + self.widget .overlay(layout) - .map(|overlay| overlay.map(self.mapper.clone())) + .map(move |overlay| overlay.map(mapper)) } } @@ -450,7 +452,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { self.element.overlay(layout) } } diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 9c180671..2a19e286 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -1,5 +1,5 @@ use crate::{ - container, layout, mouse, overlay, scrollable, Clipboard, Container, + container, layout, mouse, overlay, scrollable, text, Clipboard, Container, Element, Event, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size, Vector, Widget, }; @@ -39,10 +39,10 @@ where pub fn new( state: &'a mut State, options: impl Into>, - on_selected: Box Message>, + on_selected: &'a dyn Fn(T) -> Message, width: u16, target_height: f32, - text_size: u16, + text_size: Option, padding: u16, style: ::Style, ) -> Self @@ -175,8 +175,8 @@ where { hovered_option: &'a mut Option, options: Cow<'a, [T]>, - on_selected: Box Message>, - text_size: u16, + on_selected: &'a dyn Fn(T) -> Message, + text_size: Option, padding: u16, style: ::Style, } @@ -188,8 +188,8 @@ where pub fn new( hovered_option: &'a mut Option, options: impl Into>, - on_selected: Box Message>, - text_size: u16, + on_selected: &'a dyn Fn(T) -> Message, + text_size: Option, padding: u16, style: ::Style, ) -> Self { @@ -221,17 +221,18 @@ where fn layout( &self, - _renderer: &Renderer, + renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { use std::f32; let limits = limits.width(Length::Fill).height(Length::Shrink); + let text_size = self.text_size.unwrap_or(renderer.default_size()); let size = { let intrinsic = Size::new( 0.0, - f32::from(self.text_size + self.padding * 2) + f32::from(text_size + self.padding * 2) * self.options.len() as f32, ); @@ -253,7 +254,7 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec, - _renderer: &Renderer, + renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, ) { match event { @@ -270,11 +271,13 @@ where } Event::Mouse(mouse::Event::CursorMoved { .. }) => { let bounds = layout.bounds(); + let text_size = + self.text_size.unwrap_or(renderer.default_size()); if bounds.contains(cursor_position) { *self.hovered_option = Some( ((cursor_position.y - bounds.y) - / f32::from(self.text_size + self.padding * 2)) + / f32::from(text_size + self.padding * 2)) as usize, ); } @@ -296,14 +299,16 @@ where cursor_position, &self.options, *self.hovered_option, - self.text_size, + self.text_size.unwrap_or(renderer.default_size()), self.padding, &self.style, ) } } -pub trait Renderer: scrollable::Renderer + container::Renderer { +pub trait Renderer: + scrollable::Renderer + container::Renderer + text::Renderer +{ type Style: Default + Clone; fn decorate( diff --git a/native/src/program/state.rs b/native/src/program/state.rs index fdc42e8b..ddbbbb59 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -35,7 +35,7 @@ where renderer: &mut P::Renderer, debug: &mut Debug, ) -> Self { - let user_interface = build_user_interface( + let mut user_interface = build_user_interface( &mut program, Cache::default(), renderer, @@ -153,7 +153,7 @@ where command })); - let user_interface = build_user_interface( + let mut user_interface = build_user_interface( &mut self.program, temp_cache, renderer, diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 12cea684..9ec6bb96 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,4 +1,4 @@ -use crate::{layout, Clipboard, Element, Event, Layout, Overlay, Point, Size}; +use crate::{layout, Clipboard, Element, Event, Layout, Point, Size}; use std::hash::Hasher; @@ -19,13 +19,13 @@ use std::hash::Hasher; /// [`UserInterface`]: struct.UserInterface.html #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { - base: Layer>, - overlay: Option>>, + root: Element<'a, Message, Renderer>, + base: Layer, + overlay: Option, bounds: Size, } -struct Layer { - root: T, +struct Layer { layout: layout::Node, hash: u64, } @@ -97,9 +97,9 @@ where cache: Cache, renderer: &mut Renderer, ) -> Self { - let mut root = root.into(); + let root = root.into(); - let (base, overlay) = { + let base = { let hash = { let hasher = &mut crate::Hasher::default(); root.hash_layout(hasher); @@ -115,27 +115,13 @@ where renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)) }; - let overlay = root.overlay(Layout::new(&layout)); - - (Layer { root, layout, hash }, overlay) + Layer { layout, hash } }; - let overlay = overlay.map(|root| { - let hash = { - let hasher = &mut crate::Hasher::default(); - root.hash_layout(hasher); - - hasher.finish() - }; - - let layout = root.layout(&renderer, bounds); - - Layer { root, layout, hash } - }); - UserInterface { + root, base, - overlay, + overlay: None, bounds, } } @@ -215,35 +201,49 @@ where ) -> Vec { let mut messages = Vec::new(); - for event in events { - if let Some(overlay) = &mut self.overlay { - let base_cursor = - if overlay.layout.bounds().contains(cursor_position) { - // TODO: Encode cursor availability - Point::new(-1.0, -1.0) - } else { - cursor_position - }; + let base_events = if let Some(mut overlay) = + self.root.overlay(Layout::new(&self.base.layout)) + { + let layer = { + let new_hash = { + let hasher = &mut crate::Hasher::default(); + overlay.hash_layout(hasher); - overlay.root.on_event( + hasher.finish() + }; + + let layout = match self.overlay.take() { + Some(Layer { hash, layout }) if new_hash == hash => layout, + _ => overlay.layout(&renderer, self.bounds), + }; + + Layer { + layout, + hash: new_hash, + } + }; + + for event in events { + overlay.on_event( event.clone(), - Layout::new(&overlay.layout), + Layout::new(&layer.layout), cursor_position, &mut messages, renderer, clipboard, ); + } - self.base.root.widget.on_event( - event, - Layout::new(&self.base.layout), - base_cursor, - &mut messages, - renderer, - clipboard, - ); - } else { - self.base.root.widget.on_event( + self.overlay = Some(layer); + + None + } else { + Some(events) + }; + + if let Some(events) = base_events { + for event in events { + self.root.widget.on_event( event, Layout::new(&self.base.layout), cursor_position, @@ -327,12 +327,12 @@ where /// } /// ``` pub fn draw( - &self, + &mut self, renderer: &mut Renderer, cursor_position: Point, ) -> Renderer::Output { - if let Some(overlay) = &self.overlay { - let overlay_bounds = overlay.layout.bounds(); + if let Some(layer) = &self.overlay { + let overlay_bounds = layer.layout.bounds(); let base_cursor = if overlay_bounds.contains(cursor_position) { Point::new(-1.0, -1.0) @@ -340,27 +340,33 @@ where cursor_position }; - let base_primitives = self.base.root.widget.draw( + let base_primitives = self.root.widget.draw( renderer, &Renderer::Defaults::default(), Layout::new(&self.base.layout), base_cursor, ); - let overlay_primitives = overlay.root.draw( - renderer, - &Renderer::Defaults::default(), - Layout::new(&overlay.layout), - cursor_position, - ); + if let Some(overlay) = + self.root.overlay(Layout::new(&self.base.layout)) + { + let overlay_primitives = overlay.draw( + renderer, + &Renderer::Defaults::default(), + Layout::new(&layer.layout), + cursor_position, + ); - renderer.overlay( - base_primitives, - overlay_primitives, - overlay_bounds, - ) + renderer.overlay( + base_primitives, + overlay_primitives, + overlay_bounds, + ) + } else { + base_primitives + } } else { - self.base.root.widget.draw( + self.root.widget.draw( renderer, &Renderer::Defaults::default(), Layout::new(&self.base.layout), diff --git a/native/src/widget.rs b/native/src/widget.rs index 664a0cfd..4bca7722 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -179,10 +179,10 @@ where ) { } - fn overlay( - &mut self, + fn overlay<'b>( + &'b mut self, _layout: Layout<'_>, - ) -> Option> { + ) -> Option> { None } } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 9a6dbdb3..e83ef93d 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -208,7 +208,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { self.children .iter_mut() .zip(layout.children()) diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index df2a530a..4a509354 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -10,7 +10,7 @@ pub struct ComboBox<'a, T, Message, Renderer: self::Renderer> where [T]: ToOwned>, { - internal: Option>, + internal: Internal<'a, T, Message>, options: Cow<'a, [T]>, selected: Option, width: Length, @@ -42,10 +42,10 @@ where on_selected: impl Fn(T) -> Message + 'static, ) -> Self { Self { - internal: Some(Internal { + internal: Internal { menu: &mut state.menu, on_selected: Box::new(on_selected), - }), + }, options: options.into(), selected, width: Length::Shrink, @@ -180,16 +180,14 @@ where ) { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - if let Some(internal) = &mut self.internal { - if layout.bounds().contains(cursor_position) { - let selected = self.selected.as_ref(); - - internal.menu.open( - self.options - .iter() - .position(|option| Some(option) == selected), - ); - } + if layout.bounds().contains(cursor_position) { + let selected = self.selected.as_ref(); + + self.internal.menu.open( + self.options + .iter() + .position(|option| Some(option) == selected), + ); } } _ => {} @@ -217,33 +215,23 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { - let is_open = self - .internal - .as_ref() - .map(|internal| internal.menu.is_open()) - .unwrap_or(false); - - if is_open { - if let Some(Internal { menu, on_selected }) = self.internal.take() { - let bounds = layout.bounds(); - - Some(Overlay::new( - layout.position(), - Box::new(Menu::new( - menu, - self.options.clone(), - on_selected, - bounds.width.round() as u16, - bounds.height, - self.text_size.unwrap_or(20), - self.padding, - Renderer::menu_style(&self.style), - )), - )) - } else { - None - } + ) -> Option> { + if self.internal.menu.is_open() { + let bounds = layout.bounds(); + + Some(Overlay::new( + layout.position(), + Box::new(Menu::new( + self.internal.menu, + self.options.clone(), + &self.internal.on_selected, + bounds.width.round() as u16, + bounds.height, + self.text_size, + self.padding, + Renderer::menu_style(&self.style), + )), + )) } else { None } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 3bdb1a27..4ab10837 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -218,7 +218,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { self.content.overlay(layout.children().next().unwrap()) } } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 25bd641f..1cfe2d66 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -210,7 +210,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { self.children .iter_mut() .zip(layout.children()) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 92e5265a..87871e28 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -319,14 +319,16 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { - self.content + ) -> Option> { + 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 = self.state.offset(bounds, content_bounds); + let offset = state.offset(bounds, content_bounds); overlay.translate(Vector::new(0.0, -(offset as f32))) }) -- cgit From 9fa0b4da5d5356cb8bd4e79911a939fee0104790 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Jul 2020 05:54:49 +0200 Subject: Complete `hash_layout` for `menu::List` --- native/src/overlay/menu.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 2a19e286..32cbacce 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -113,6 +113,9 @@ where fn hash_layout(&self, state: &mut Hasher, position: Point) { use std::hash::Hash; + struct Marker; + std::any::TypeId::of::().hash(state); + (position.x as u32).hash(state); (position.y as u32).hash(state); } @@ -138,10 +141,11 @@ where clipboard, ); + let option_was_selected = current_messages < messages.len(); + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - if !bounds.contains(cursor_position) - || current_messages < messages.len() => + if !bounds.contains(cursor_position) || option_was_selected => { *self.is_open = false; } @@ -245,7 +249,12 @@ where fn hash_layout(&self, state: &mut Hasher) { use std::hash::Hash as _; - 0.hash(state); + struct Marker; + std::any::TypeId::of::().hash(state); + + self.options.len().hash(state); + self.text_size.hash(state); + self.padding.hash(state); } fn on_event( -- cgit From 7a105ade27c7f33397e3050280e9faef928bdc1b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Jul 2020 06:14:57 +0200 Subject: Use `Borrow` to avoid clone in `ComboBox::overlay` --- native/src/overlay/menu.rs | 35 ++++++++++++++--------------------- native/src/widget/combo_box.rs | 2 +- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 32cbacce..ffa3b14c 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -3,7 +3,7 @@ use crate::{ Element, Event, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size, Vector, Widget, }; -use std::borrow::Cow; +use std::borrow::Borrow; pub struct Menu<'a, Message, Renderer: self::Renderer> { container: Container<'a, Message, Renderer>, @@ -36,9 +36,9 @@ where Message: 'static, Renderer: 'a, { - pub fn new( + pub fn new( state: &'a mut State, - options: impl Into>, + options: &'a dyn Borrow<[T]>, on_selected: &'a dyn Fn(T) -> Message, width: u16, target_height: f32, @@ -48,7 +48,7 @@ where ) -> Self where T: Clone + ToString, - [T]: ToOwned>, + [T]: ToOwned, { let container = Container::new( Scrollable::new(&mut state.scrollable).push(List::new( @@ -173,25 +173,19 @@ where } } -struct List<'a, T, Message, Renderer: self::Renderer> -where - [T]: ToOwned, -{ +struct List<'a, T, Message, Renderer: self::Renderer> { hovered_option: &'a mut Option, - options: Cow<'a, [T]>, + options: &'a dyn Borrow<[T]>, on_selected: &'a dyn Fn(T) -> Message, text_size: Option, padding: u16, style: ::Style, } -impl<'a, T, Message, Renderer: self::Renderer> List<'a, T, Message, Renderer> -where - [T]: ToOwned, -{ +impl<'a, T, Message, Renderer: self::Renderer> List<'a, T, Message, Renderer> { pub fn new( hovered_option: &'a mut Option, - options: impl Into>, + options: &'a dyn Borrow<[T]>, on_selected: &'a dyn Fn(T) -> Message, text_size: Option, padding: u16, @@ -199,7 +193,7 @@ where ) -> Self { List { hovered_option, - options: options.into(), + options, on_selected, text_size, padding, @@ -211,8 +205,7 @@ where impl<'a, T, Message, Renderer: self::Renderer> Widget<'a, Message, Renderer> for List<'a, T, Message, Renderer> where - T: ToString + Clone, - [T]: ToOwned, + T: Clone + ToString, Renderer: self::Renderer, { fn width(&self) -> Length { @@ -237,7 +230,7 @@ where let intrinsic = Size::new( 0.0, f32::from(text_size + self.padding * 2) - * self.options.len() as f32, + * self.options.borrow().len() as f32, ); limits.resolve(intrinsic) @@ -252,7 +245,7 @@ where struct Marker; std::any::TypeId::of::().hash(state); - self.options.len().hash(state); + self.options.borrow().len().hash(state); self.text_size.hash(state); self.padding.hash(state); } @@ -272,7 +265,7 @@ where if bounds.contains(cursor_position) { if let Some(index) = *self.hovered_option { - if let Some(option) = self.options.get(index) { + if let Some(option) = self.options.borrow().get(index) { messages.push((self.on_selected)(option.clone())); } } @@ -306,7 +299,7 @@ where renderer, layout.bounds(), cursor_position, - &self.options, + self.options.borrow(), *self.hovered_option, self.text_size.unwrap_or(renderer.default_size()), self.padding, diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index 4a509354..f2dc86e8 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -223,7 +223,7 @@ where layout.position(), Box::new(Menu::new( self.internal.menu, - self.options.clone(), + &self.options, &self.internal.on_selected, bounds.width.round() as u16, bounds.height, -- cgit From 1c12bad866d06b320f16609576d5937413418a0c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Jul 2020 06:55:22 +0200 Subject: Split `Menu::new` into multiple builder methods --- native/src/overlay.rs | 2 +- native/src/overlay/menu.rs | 124 +++++++++++++++++++++++++++++++---------- native/src/widget/combo_box.rs | 42 ++++++-------- 3 files changed, 114 insertions(+), 54 deletions(-) diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 96390348..b8c8bb48 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -32,7 +32,7 @@ where pub fn map(self, f: Rc B>) -> Overlay<'a, B, Renderer> where - Message: 'static, + Message: 'a, Renderer: 'a, B: 'static, { diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index ffa3b14c..8475f130 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -3,16 +3,74 @@ use crate::{ Element, Event, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size, Vector, Widget, }; -use std::borrow::Borrow; -pub struct Menu<'a, Message, Renderer: self::Renderer> { - container: Container<'a, Message, Renderer>, - is_open: &'a mut bool, +pub struct Menu<'a, T, Message, Renderer: self::Renderer> { + state: &'a mut State, + options: &'a [T], + on_selected: &'a dyn Fn(T) -> Message, width: u16, - target_height: f32, + padding: u16, + text_size: Option, style: ::Style, } +impl<'a, T, Message, Renderer> Menu<'a, T, Message, Renderer> +where + T: ToString + Clone, + Message: 'a, + Renderer: self::Renderer + 'a, +{ + pub fn new( + state: &'a mut State, + options: &'a [T], + on_selected: &'a dyn Fn(T) -> Message, + ) -> Self { + Menu { + state, + options, + on_selected, + width: 0, + padding: 0, + text_size: None, + style: Default::default(), + } + } + + pub fn width(mut self, width: u16) -> Self { + self.width = width; + self + } + + pub fn padding(mut self, padding: u16) -> Self { + self.padding = padding; + self + } + + pub fn text_size(mut self, text_size: u16) -> Self { + self.text_size = Some(text_size); + self + } + + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { + self.style = style.into(); + self + } + + pub fn overlay( + self, + position: Point, + target_height: f32, + ) -> overlay::Overlay<'a, Message, Renderer> { + overlay::Overlay::new( + position, + Box::new(Overlay::new(self, target_height)), + ) + } +} + #[derive(Default)] pub struct State { scrollable: scrollable::State, @@ -31,29 +89,40 @@ impl State { } } -impl<'a, Message, Renderer: self::Renderer> Menu<'a, Message, Renderer> +struct Overlay<'a, Message, Renderer: self::Renderer> { + container: Container<'a, Message, Renderer>, + is_open: &'a mut bool, + width: u16, + target_height: f32, + style: ::Style, +} + +impl<'a, Message, Renderer: self::Renderer> Overlay<'a, Message, Renderer> where - Message: 'static, + Message: 'a, Renderer: 'a, { pub fn new( - state: &'a mut State, - options: &'a dyn Borrow<[T]>, - on_selected: &'a dyn Fn(T) -> Message, - width: u16, + menu: Menu<'a, T, Message, Renderer>, target_height: f32, - text_size: Option, - padding: u16, - style: ::Style, ) -> Self where T: Clone + ToString, - [T]: ToOwned, { + let Menu { + state, + options, + on_selected, + width, + padding, + text_size, + style, + } = menu; + let container = Container::new( Scrollable::new(&mut state.scrollable).push(List::new( - &mut state.hovered_option, options, + &mut state.hovered_option, on_selected, text_size, padding, @@ -65,15 +134,15 @@ where Self { container, is_open: &mut state.is_open, - width, + width: width, target_height, - style, + style: style, } } } impl<'a, Message, Renderer> overlay::Content - for Menu<'a, Message, Renderer> + for Overlay<'a, Message, Renderer> where Renderer: self::Renderer, { @@ -174,8 +243,8 @@ where } struct List<'a, T, Message, Renderer: self::Renderer> { + options: &'a [T], hovered_option: &'a mut Option, - options: &'a dyn Borrow<[T]>, on_selected: &'a dyn Fn(T) -> Message, text_size: Option, padding: u16, @@ -184,16 +253,16 @@ struct List<'a, T, Message, Renderer: self::Renderer> { impl<'a, T, Message, Renderer: self::Renderer> List<'a, T, Message, Renderer> { pub fn new( + options: &'a [T], hovered_option: &'a mut Option, - options: &'a dyn Borrow<[T]>, on_selected: &'a dyn Fn(T) -> Message, text_size: Option, padding: u16, style: ::Style, ) -> Self { List { - hovered_option, options, + hovered_option, on_selected, text_size, padding, @@ -230,7 +299,7 @@ where let intrinsic = Size::new( 0.0, f32::from(text_size + self.padding * 2) - * self.options.borrow().len() as f32, + * self.options.len() as f32, ); limits.resolve(intrinsic) @@ -245,7 +314,7 @@ where struct Marker; std::any::TypeId::of::().hash(state); - self.options.borrow().len().hash(state); + self.options.len().hash(state); self.text_size.hash(state); self.padding.hash(state); } @@ -265,7 +334,7 @@ where if bounds.contains(cursor_position) { if let Some(index) = *self.hovered_option { - if let Some(option) = self.options.borrow().get(index) { + if let Some(option) = self.options.get(index) { messages.push((self.on_selected)(option.clone())); } } @@ -299,7 +368,7 @@ where renderer, layout.bounds(), cursor_position, - self.options.borrow(), + self.options, *self.hovered_option, self.text_size.unwrap_or(renderer.default_size()), self.padding, @@ -337,8 +406,7 @@ impl<'a, T, Message, Renderer> Into> for List<'a, T, Message, Renderer> where T: ToString + Clone, - [T]: ToOwned, - Message: 'static, + Message: 'a, Renderer: 'a + self::Renderer, { fn into(self) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index f2dc86e8..f6da076a 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -10,7 +10,8 @@ pub struct ComboBox<'a, T, Message, Renderer: self::Renderer> where [T]: ToOwned>, { - internal: Internal<'a, T, Message>, + menu: &'a mut menu::State, + on_selected: Box Message>, options: Cow<'a, [T]>, selected: Option, width: Length, @@ -24,11 +25,6 @@ pub struct State { menu: menu::State, } -pub struct Internal<'a, T, Message> { - menu: &'a mut menu::State, - on_selected: Box Message>, -} - impl<'a, T: 'a, Message, Renderer: self::Renderer> ComboBox<'a, T, Message, Renderer> where @@ -42,10 +38,8 @@ where on_selected: impl Fn(T) -> Message + 'static, ) -> Self { Self { - internal: Internal { - menu: &mut state.menu, - on_selected: Box::new(on_selected), - }, + menu: &mut state.menu, + on_selected: Box::new(on_selected), options: options.into(), selected, width: Length::Shrink, @@ -183,7 +177,7 @@ where if layout.bounds().contains(cursor_position) { let selected = self.selected.as_ref(); - self.internal.menu.open( + self.menu.open( self.options .iter() .position(|option| Some(option) == selected), @@ -216,22 +210,20 @@ where &mut self, layout: Layout<'_>, ) -> Option> { - if self.internal.menu.is_open() { + if self.menu.is_open() { let bounds = layout.bounds(); - Some(Overlay::new( - layout.position(), - Box::new(Menu::new( - self.internal.menu, - &self.options, - &self.internal.on_selected, - bounds.width.round() as u16, - bounds.height, - self.text_size, - self.padding, - Renderer::menu_style(&self.style), - )), - )) + let mut menu = + Menu::new(&mut self.menu, &self.options, &self.on_selected) + .width(bounds.width.round() as u16) + .padding(self.padding) + .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 } -- cgit From 69ac47f463fd5c392f1f8e788fcf89b1a76abcae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Jul 2020 07:04:20 +0200 Subject: Implement `font` method for `ComboBox` --- graphics/src/overlay/menu.rs | 5 ++-- graphics/src/widget/combo_box.rs | 5 ++-- native/src/overlay/menu.rs | 51 +++++++++++++++++----------------------- native/src/widget/combo_box.rs | 15 +++++++++--- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index 914baa6a..89a9cd03 100644 --- a/graphics/src/overlay/menu.rs +++ b/graphics/src/overlay/menu.rs @@ -43,8 +43,9 @@ where cursor_position: Point, options: &[T], hovered_option: Option, - text_size: u16, padding: u16, + text_size: u16, + font: Font, style: &Style, ) -> Self::Output { use std::f32; @@ -83,7 +84,7 @@ where ..bounds }, size: f32::from(text_size), - font: Font::Default, + font, color: if is_selected { style.selected_text_color } else { diff --git a/graphics/src/widget/combo_box.rs b/graphics/src/widget/combo_box.rs index 078b5def..e7ed4e04 100644 --- a/graphics/src/widget/combo_box.rs +++ b/graphics/src/widget/combo_box.rs @@ -29,8 +29,9 @@ where bounds: Rectangle, cursor_position: Point, selected: Option, - text_size: u16, padding: u16, + text_size: u16, + font: Font, style: &Box, ) -> Self::Output { let is_mouse_over = bounds.contains(cursor_position); @@ -69,7 +70,7 @@ where let label = Primitive::Text { content: label, size: f32::from(text_size), - font: Font::Default, + font, color: style.text_color, bounds: Rectangle { x: bounds.x + f32::from(padding), diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 8475f130..a192e389 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -11,6 +11,7 @@ pub struct Menu<'a, T, Message, Renderer: self::Renderer> { width: u16, padding: u16, text_size: Option, + font: Renderer::Font, style: ::Style, } @@ -32,6 +33,7 @@ where width: 0, padding: 0, text_size: None, + font: Default::default(), style: Default::default(), } } @@ -51,6 +53,11 @@ where self } + pub fn font(mut self, font: Renderer::Font) -> Self { + self.font = font; + self + } + pub fn style( mut self, style: impl Into<::Style>, @@ -115,21 +122,22 @@ where on_selected, width, padding, + font, text_size, style, } = menu; - let container = Container::new( - Scrollable::new(&mut state.scrollable).push(List::new( + let container = + Container::new(Scrollable::new(&mut state.scrollable).push(List { options, - &mut state.hovered_option, + hovered_option: &mut state.hovered_option, on_selected, + font, text_size, padding, - style.clone(), - )), - ) - .padding(1); + style: style.clone(), + })) + .padding(1); Self { container, @@ -246,31 +254,12 @@ struct List<'a, T, Message, Renderer: self::Renderer> { options: &'a [T], hovered_option: &'a mut Option, on_selected: &'a dyn Fn(T) -> Message, - text_size: Option, padding: u16, + text_size: Option, + font: Renderer::Font, style: ::Style, } -impl<'a, T, Message, Renderer: self::Renderer> List<'a, T, Message, Renderer> { - pub fn new( - options: &'a [T], - hovered_option: &'a mut Option, - on_selected: &'a dyn Fn(T) -> Message, - text_size: Option, - padding: u16, - style: ::Style, - ) -> Self { - List { - options, - hovered_option, - on_selected, - text_size, - padding, - style, - } - } -} - impl<'a, T, Message, Renderer: self::Renderer> Widget<'a, Message, Renderer> for List<'a, T, Message, Renderer> where @@ -370,8 +359,9 @@ where cursor_position, self.options, *self.hovered_option, - self.text_size.unwrap_or(renderer.default_size()), self.padding, + self.text_size.unwrap_or(renderer.default_size()), + self.font, &self.style, ) } @@ -396,8 +386,9 @@ pub trait Renderer: cursor_position: Point, options: &[T], hovered_option: Option, - text_size: u16, padding: u16, + text_size: u16, + font: Self::Font, style: &::Style, ) -> Self::Output; } diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index f6da076a..84789789 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -17,6 +17,7 @@ where width: Length, padding: u16, text_size: Option, + font: Renderer::Font, style: ::Style, } @@ -45,7 +46,8 @@ where width: Length::Shrink, text_size: None, padding: Renderer::DEFAULT_PADDING, - style: ::Style::default(), + font: Default::default(), + style: Default::default(), } } @@ -70,6 +72,11 @@ where self } + pub fn font(mut self, font: Renderer::Font) -> Self { + self.font = font; + self + } + /// Sets the style of the [`ComboBox`]. /// /// [`ComboBox`]: struct.ComboBox.html @@ -200,8 +207,9 @@ where layout.bounds(), cursor_position, self.selected.as_ref().map(ToString::to_string), - self.text_size.unwrap_or(renderer.default_size()), self.padding, + self.text_size.unwrap_or(renderer.default_size()), + self.font, &self.style, ) } @@ -244,8 +252,9 @@ pub trait Renderer: text::Renderer + menu::Renderer { bounds: Rectangle, cursor_position: Point, selected: Option, - text_size: u16, padding: u16, + text_size: u16, + font: Self::Font, style: &::Style, ) -> Self::Output; } -- cgit From aa0ec2821ef9439adb52a2712da553404eb11b4d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Jul 2020 08:09:14 +0200 Subject: Finish wiring overlays to `UserInterface` --- native/src/program/state.rs | 4 +- native/src/user_interface.rs | 194 +++++++++++++++++++++++++------------------ 2 files changed, 116 insertions(+), 82 deletions(-) diff --git a/native/src/program/state.rs b/native/src/program/state.rs index ddbbbb59..95e6b60c 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -121,12 +121,14 @@ where debug.event_processing_started(); let mut messages = user_interface.update( - self.queued_events.drain(..), + &self.queued_events, cursor_position, clipboard, renderer, ); messages.extend(self.queued_messages.drain(..)); + + self.queued_events.clear(); debug.event_processing_finished(); if messages.is_empty() { diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 9ec6bb96..6e56f357 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,4 +1,4 @@ -use crate::{layout, Clipboard, Element, Event, Layout, Point, Size}; +use crate::{layout, Clipboard, Element, Event, Layout, Overlay, Point, Size}; use std::hash::Hasher; @@ -25,11 +25,6 @@ pub struct UserInterface<'a, Message, Renderer> { bounds: Size, } -struct Layer { - layout: layout::Node, - hash: u64, -} - impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> where Renderer: crate::Renderer, @@ -99,7 +94,7 @@ where ) -> Self { let root = root.into(); - let base = { + let (base, overlay) = { let hash = { let hasher = &mut crate::Hasher::default(); root.hash_layout(hasher); @@ -107,21 +102,28 @@ where hasher.finish() }; - let layout_is_cached = hash == cache.hash && bounds == cache.bounds; + let layout_is_cached = + hash == cache.base.hash && bounds == cache.bounds; - let layout = if layout_is_cached { - cache.layout + let (layout, overlay) = if layout_is_cached { + (cache.base.layout, cache.overlay) } else { - renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)) + ( + renderer.layout( + &root, + &layout::Limits::new(Size::ZERO, bounds), + ), + None, + ) }; - Layer { layout, hash } + (Layer { layout, hash }, overlay) }; UserInterface { root, base, - overlay: None, + overlay, bounds, } } @@ -178,7 +180,7 @@ where /// /// // Update the user interface /// let messages = user_interface.update( - /// events.drain(..), + /// &events, /// cursor_position, /// None, /// &renderer, @@ -194,34 +196,22 @@ where /// ``` pub fn update( &mut self, - events: impl IntoIterator, + events: &[Event], cursor_position: Point, clipboard: Option<&dyn Clipboard>, renderer: &Renderer, ) -> Vec { let mut messages = Vec::new(); - let base_events = if let Some(mut overlay) = + let base_cursor = if let Some(mut overlay) = self.root.overlay(Layout::new(&self.base.layout)) { - let layer = { - let new_hash = { - let hasher = &mut crate::Hasher::default(); - overlay.hash_layout(hasher); - - hasher.finish() - }; - - let layout = match self.overlay.take() { - Some(Layer { hash, layout }) if new_hash == hash => layout, - _ => overlay.layout(&renderer, self.bounds), - }; - - Layer { - layout, - hash: new_hash, - } - }; + let layer = Self::overlay_layer( + self.overlay.take(), + self.bounds, + &mut overlay, + renderer, + ); for event in events { overlay.on_event( @@ -234,24 +224,30 @@ where ); } + let base_cursor = if layer.layout.bounds().contains(cursor_position) + { + // TODO: Type-safe cursor availability + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + self.overlay = Some(layer); - None + base_cursor } else { - Some(events) + cursor_position }; - if let Some(events) = base_events { - for event in events { - self.root.widget.on_event( - event, - Layout::new(&self.base.layout), - cursor_position, - &mut messages, - renderer, - clipboard, - ); - } + for event in events { + self.root.widget.on_event( + event.clone(), + Layout::new(&self.base.layout), + base_cursor, + &mut messages, + renderer, + clipboard, + ); } messages @@ -307,7 +303,7 @@ where /// ); /// /// let messages = user_interface.update( - /// events.drain(..), + /// &events, /// cursor_position, /// None, /// &renderer, @@ -331,9 +327,33 @@ where renderer: &mut Renderer, cursor_position: Point, ) -> Renderer::Output { - if let Some(layer) = &self.overlay { + let overlay = if let Some(mut overlay) = + self.root.overlay(Layout::new(&self.base.layout)) + { + let layer = Self::overlay_layer( + self.overlay.take(), + self.bounds, + &mut overlay, + renderer, + ); + let overlay_bounds = layer.layout.bounds(); + let overlay_primitives = overlay.draw( + renderer, + &Renderer::Defaults::default(), + Layout::new(&layer.layout), + cursor_position, + ); + + self.overlay = Some(layer); + + Some((overlay_primitives, overlay_bounds)) + } else { + None + }; + + if let Some((overlay_primitives, overlay_bounds)) = overlay { let base_cursor = if overlay_bounds.contains(cursor_position) { Point::new(-1.0, -1.0) } else { @@ -347,24 +367,11 @@ where base_cursor, ); - if let Some(overlay) = - self.root.overlay(Layout::new(&self.base.layout)) - { - let overlay_primitives = overlay.draw( - renderer, - &Renderer::Defaults::default(), - Layout::new(&layer.layout), - cursor_position, - ); - - renderer.overlay( - base_primitives, - overlay_primitives, - overlay_bounds, - ) - } else { - base_primitives - } + renderer.overlay( + base_primitives, + overlay_primitives, + overlay_bounds, + ) } else { self.root.widget.draw( renderer, @@ -382,11 +389,41 @@ where /// [`UserInterface`]: struct.UserInterface.html pub fn into_cache(self) -> Cache { Cache { - hash: self.base.hash, - layout: self.base.layout, + base: self.base, + overlay: self.overlay, bounds: self.bounds, } } + + fn overlay_layer( + cache: Option, + bounds: Size, + overlay: &mut Overlay<'_, Message, Renderer>, + renderer: &Renderer, + ) -> Layer { + let new_hash = { + let hasher = &mut crate::Hasher::default(); + overlay.hash_layout(hasher); + + hasher.finish() + }; + + let layout = match cache { + Some(Layer { hash, layout }) if new_hash == hash => layout, + _ => overlay.layout(renderer, bounds), + }; + + Layer { + layout, + hash: new_hash, + } + } +} + +#[derive(Debug, Clone)] +struct Layer { + layout: layout::Node, + hash: u64, } /// Reusable data of a specific [`UserInterface`]. @@ -394,8 +431,8 @@ where /// [`UserInterface`]: struct.UserInterface.html #[derive(Debug, Clone)] pub struct Cache { - hash: u64, - layout: layout::Node, + base: Layer, + overlay: Option, bounds: Size, } @@ -409,8 +446,11 @@ impl Cache { /// [`UserInterface`]: struct.UserInterface.html pub fn new() -> Cache { Cache { - hash: 0, - layout: layout::Node::new(Size::new(0.0, 0.0)), + base: Layer { + layout: layout::Node::new(Size::new(0.0, 0.0)), + hash: 0, + }, + overlay: None, bounds: Size::ZERO, } } @@ -421,11 +461,3 @@ impl Default for Cache { Cache::new() } } - -impl PartialEq for Cache { - fn eq(&self, other: &Cache) -> bool { - self.hash == other.hash - } -} - -impl Eq for Cache {} -- cgit From 105c0fe4780233670191abe50ddc922a553ffd63 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Jul 2020 08:19:26 +0200 Subject: Propagate `Font` from `ComboBox` to `Menu` --- native/src/widget/combo_box.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index 84789789..4d020c3b 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -225,6 +225,7 @@ where 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 { -- cgit From 21b583c46899d8f31ea39ede7194abf6f61df636 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Jul 2020 08:25:56 +0200 Subject: Avoid reopening `Menu` in `ComboBox` --- native/src/widget/combo_box.rs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index 4d020c3b..9447b9dd 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -19,6 +19,7 @@ where text_size: Option, font: Renderer::Font, style: ::Style, + is_open: bool, } #[derive(Default)] @@ -38,6 +39,8 @@ where selected: Option, 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), @@ -48,6 +51,7 @@ where padding: Renderer::DEFAULT_PADDING, font: Default::default(), style: Default::default(), + is_open, } } @@ -179,19 +183,23 @@ where _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, ) { - 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), - ); + 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), + ); + } } + _ => {} } - _ => {} } } -- cgit From 33e6682882cd09dd210da123eb3b893e33bd977d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Jul 2020 11:40:07 +0200 Subject: Avoid clipping antialiasing in `Renderer::overlay` --- graphics/src/renderer.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 771f436c..5ca6c057 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -67,7 +67,11 @@ where primitives: vec![ base_primitive, Primitive::Clip { - bounds: overlay_bounds, + bounds: Rectangle { + width: overlay_bounds.width + 0.5, + height: overlay_bounds.height + 0.5, + ..overlay_bounds + }, offset: Vector::new(0, 0), content: Box::new(overlay_primitives), }, -- cgit From 1070b61f3408539f6c9cb9d265f3295e6d055db7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Jul 2020 01:31:56 +0200 Subject: Rename `overlay::Content` trait to `Overlay` The `Overlay` struct is now `overlay::Element`. --- native/src/element.rs | 8 +-- native/src/overlay.rs | 143 +++++---------------------------------- native/src/overlay/content.rs | 34 ---------- native/src/overlay/element.rs | 145 ++++++++++++++++++++++++++++++++++++++++ native/src/overlay/menu.rs | 6 +- native/src/user_interface.rs | 4 +- native/src/widget.rs | 4 +- native/src/widget/column.rs | 4 +- native/src/widget/combo_box.rs | 8 +-- native/src/widget/container.rs | 4 +- native/src/widget/row.rs | 4 +- native/src/widget/scrollable.rs | 6 +- 12 files changed, 186 insertions(+), 184 deletions(-) delete mode 100644 native/src/overlay/content.rs create mode 100644 native/src/overlay/element.rs diff --git a/native/src/element.rs b/native/src/element.rs index b00f9e55..8e0ab295 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -1,5 +1,5 @@ use crate::{ - layout, Clipboard, Color, Event, Hasher, Layout, Length, Overlay, Point, + layout, overlay, Clipboard, Color, Event, Hasher, Layout, Length, Point, Widget, }; use std::rc::Rc; @@ -276,7 +276,7 @@ where pub fn overlay<'b>( &'b mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { self.widget.overlay(layout) } } @@ -366,7 +366,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { let mapper = self.mapper.clone(); self.widget @@ -452,7 +452,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { self.element.overlay(layout) } } diff --git a/native/src/overlay.rs b/native/src/overlay.rs index b8c8bb48..b6cbbec3 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -1,101 +1,13 @@ -mod content; +mod element; pub mod menu; -pub use content::Content; +pub use element::Element; pub use menu::Menu; -use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size, Vector}; -use std::rc::Rc; +use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size}; -#[allow(missing_debug_implementations)] -pub struct Overlay<'a, Message, Renderer> { - position: Point, - content: Box + 'a>, -} - -impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - pub fn new( - position: Point, - content: Box + 'a>, - ) -> Self { - Self { position, content } - } - - pub fn translate(mut self, translation: Vector) -> Self { - self.position = self.position + translation; - self - } - - pub fn map(self, f: Rc B>) -> Overlay<'a, B, Renderer> - where - Message: 'a, - Renderer: 'a, - B: 'static, - { - Overlay { - position: self.position, - content: Box::new(Map::new(self.content, f)), - } - } - - pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node { - self.content.layout(renderer, bounds, self.position) - } - - pub fn draw( - &self, - renderer: &mut Renderer, - defaults: &Renderer::Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> Renderer::Output { - self.content - .draw(renderer, defaults, layout, cursor_position) - } - - pub fn hash_layout(&self, state: &mut Hasher) { - self.content.hash_layout(state, self.position); - } - - pub fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, - ) { - self.content.on_event( - event, - layout, - cursor_position, - messages, - renderer, - clipboard, - ) - } -} - -struct Map<'a, A, B, Renderer> { - content: Box + 'a>, - mapper: Rc B>, -} - -impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { - pub fn new( - content: Box + 'a>, - mapper: Rc B + 'static>, - ) -> Map<'a, A, B, Renderer> { - Map { content, mapper } - } -} - -impl<'a, A, B, Renderer> Content for Map<'a, A, B, Renderer> +pub trait Overlay where Renderer: crate::Renderer, { @@ -104,34 +16,7 @@ where renderer: &Renderer, bounds: Size, position: Point, - ) -> layout::Node { - self.content.layout(renderer, bounds, position) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, - ) { - let mut original_messages = Vec::new(); - - self.content.on_event( - event, - layout, - cursor_position, - &mut original_messages, - renderer, - clipboard, - ); - - original_messages - .drain(..) - .for_each(|message| messages.push((self.mapper)(message))); - } + ) -> layout::Node; fn draw( &self, @@ -139,12 +24,18 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, - ) -> Renderer::Output { - self.content - .draw(renderer, defaults, layout, cursor_position) - } + ) -> Renderer::Output; + + fn hash_layout(&self, state: &mut Hasher, position: Point); - fn hash_layout(&self, state: &mut Hasher, position: Point) { - self.content.hash_layout(state, position); + fn on_event( + &mut self, + _event: Event, + _layout: Layout<'_>, + _cursor_position: Point, + _messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { } } diff --git a/native/src/overlay/content.rs b/native/src/overlay/content.rs deleted file mode 100644 index 5259c4b8..00000000 --- a/native/src/overlay/content.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size}; - -pub trait Content -where - Renderer: crate::Renderer, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node; - - fn draw( - &self, - renderer: &mut Renderer, - defaults: &Renderer::Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> Renderer::Output; - - fn hash_layout(&self, state: &mut Hasher, position: Point); - - fn on_event( - &mut self, - _event: Event, - _layout: Layout<'_>, - _cursor_position: Point, - _messages: &mut Vec, - _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, - ) { - } -} diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs new file mode 100644 index 00000000..a159e3c1 --- /dev/null +++ b/native/src/overlay/element.rs @@ -0,0 +1,145 @@ +pub use crate::Overlay; + +use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size, Vector}; +use std::rc::Rc; + +#[allow(missing_debug_implementations)] +pub struct Element<'a, Message, Renderer> { + position: Point, + overlay: Box + 'a>, +} + +impl<'a, Message, Renderer> Element<'a, Message, Renderer> +where + Renderer: crate::Renderer, +{ + pub fn new( + position: Point, + overlay: Box + 'a>, + ) -> Self { + Self { position, overlay } + } + + pub fn translate(mut self, translation: Vector) -> Self { + self.position = self.position + translation; + self + } + + pub fn map(self, f: Rc B>) -> Element<'a, B, Renderer> + where + Message: 'a, + Renderer: 'a, + B: 'static, + { + Element { + position: self.position, + overlay: Box::new(Map::new(self.overlay, f)), + } + } + + pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node { + self.overlay.layout(renderer, bounds, self.position) + } + + pub fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + self.overlay + .draw(renderer, defaults, layout, cursor_position) + } + + pub fn hash_layout(&self, state: &mut Hasher) { + self.overlay.hash_layout(state, self.position); + } + + pub fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, + ) { + self.overlay.on_event( + event, + layout, + cursor_position, + messages, + renderer, + clipboard, + ) + } +} + +struct Map<'a, A, B, Renderer> { + content: Box + 'a>, + mapper: Rc B>, +} + +impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { + pub fn new( + content: Box + 'a>, + mapper: Rc B + 'static>, + ) -> Map<'a, A, B, Renderer> { + Map { content, mapper } + } +} + +impl<'a, A, B, Renderer> Overlay for Map<'a, A, B, Renderer> +where + Renderer: crate::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + self.content.layout(renderer, bounds, position) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, + ) { + let mut original_messages = Vec::new(); + + self.content.on_event( + event, + layout, + cursor_position, + &mut original_messages, + renderer, + clipboard, + ); + + original_messages + .drain(..) + .for_each(|message| messages.push((self.mapper)(message))); + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + self.content + .draw(renderer, defaults, layout, cursor_position) + } + + fn hash_layout(&self, state: &mut Hasher, position: Point) { + self.content.hash_layout(state, position); + } +} diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index a192e389..24eaef38 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -70,8 +70,8 @@ where self, position: Point, target_height: f32, - ) -> overlay::Overlay<'a, Message, Renderer> { - overlay::Overlay::new( + ) -> overlay::Element<'a, Message, Renderer> { + overlay::Element::new( position, Box::new(Overlay::new(self, target_height)), ) @@ -149,7 +149,7 @@ where } } -impl<'a, Message, Renderer> overlay::Content +impl<'a, Message, Renderer> crate::Overlay for Overlay<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 6e56f357..00a290f1 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,4 +1,4 @@ -use crate::{layout, Clipboard, Element, Event, Layout, Overlay, Point, Size}; +use crate::{layout, overlay, Clipboard, Element, Event, Layout, Point, Size}; use std::hash::Hasher; @@ -398,7 +398,7 @@ where fn overlay_layer( cache: Option, bounds: Size, - overlay: &mut Overlay<'_, Message, Renderer>, + overlay: &mut overlay::Element<'_, Message, Renderer>, renderer: &Renderer, ) -> Layer { let new_hash = { diff --git a/native/src/widget.rs b/native/src/widget.rs index 4bca7722..11952018 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -70,7 +70,7 @@ pub use text::Text; #[doc(no_inline)] pub use text_input::TextInput; -use crate::{layout, Clipboard, Event, Hasher, Layout, Length, Overlay, Point}; +use crate::{layout, overlay, Clipboard, Event, Hasher, Layout, Length, Point}; /// A component that displays information and allows interaction. /// @@ -182,7 +182,7 @@ where fn overlay<'b>( &'b mut self, _layout: Layout<'_>, - ) -> Option> { + ) -> Option> { None } } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index e83ef93d..83d5054f 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use crate::{ - layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Overlay, + layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Widget, }; @@ -208,7 +208,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { self.children .iter_mut() .zip(layout.children()) diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index 9447b9dd..e33eebfd 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -1,8 +1,8 @@ use crate::{ - layout, mouse, + layout, mouse, overlay, overlay::menu::{self, Menu}, - scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, - Overlay, Point, Rectangle, Size, Widget, + scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, }; use std::borrow::Cow; @@ -225,7 +225,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { if self.menu.is_open() { let bounds = layout.bounds(); diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 4ab10837..0ee67db0 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use crate::{ - layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Overlay, + layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Widget, }; @@ -218,7 +218,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { self.content.overlay(layout.children().next().unwrap()) } } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 1cfe2d66..eda515b0 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use crate::{ - layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Overlay, + layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Widget, }; @@ -210,7 +210,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { self.children .iter_mut() .zip(layout.children()) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 87871e28..abd8cfbd 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, Overlay, Point, Rectangle, Size, Vector, Widget, + column, layout, mouse, overlay, Align, Clipboard, Column, Element, Event, + Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; use std::{f32, hash::Hash, u32}; @@ -319,7 +319,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - ) -> Option> { + ) -> Option> { let Self { content, state, .. } = self; content -- cgit From f24e03eae85cb4a684eb0a28033c70c355d2c5ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Jul 2020 01:35:58 +0200 Subject: Implement `Widget::overlay` for `PaneGrid` --- native/src/widget/pane_grid.rs | 15 +++++++++++++-- native/src/widget/pane_grid/content.rs | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 8fc423af..a8e1f852 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> { + 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> { + 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 for Content<'a, Message, Renderer> -- cgit From dc0e423142f053c59c326d92920e7829b6852cca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Jul 2020 02:01:30 +0200 Subject: Remove unnecessary lifetime in `Widget` trait --- examples/custom_widget/src/main.rs | 2 +- examples/geometry/src/main.rs | 2 +- graphics/src/renderer.rs | 2 +- graphics/src/widget/canvas.rs | 2 +- native/src/element.rs | 12 ++++++------ native/src/layout/debugger.rs | 2 +- native/src/overlay/menu.rs | 2 +- native/src/widget.rs | 8 ++++---- native/src/widget/button.rs | 2 +- native/src/widget/checkbox.rs | 2 +- native/src/widget/column.rs | 2 +- native/src/widget/combo_box.rs | 2 +- native/src/widget/container.rs | 2 +- native/src/widget/image.rs | 2 +- native/src/widget/pane_grid.rs | 2 +- native/src/widget/progress_bar.rs | 3 +-- native/src/widget/radio.rs | 3 +-- native/src/widget/row.rs | 2 +- native/src/widget/scrollable.rs | 2 +- native/src/widget/slider.rs | 2 +- native/src/widget/space.rs | 2 +- native/src/widget/svg.rs | 2 +- native/src/widget/text.rs | 2 +- native/src/widget/text_input.rs | 2 +- 24 files changed, 32 insertions(+), 34 deletions(-) diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index a6068f91..bcf896b0 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -25,7 +25,7 @@ mod circle { } } - impl<'a, Message, B> Widget<'a, Message, Renderer> for Circle + impl Widget> for Circle where B: Backend, { diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 3795323a..71ce0d8c 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -27,7 +27,7 @@ mod rainbow { } } - impl<'a, Message, B> Widget<'a, Message, Renderer> for Rainbow + impl Widget> for Rainbow where B: Backend, { diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 5ca6c057..5d51e6d4 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -93,7 +93,7 @@ where fn explain( &mut self, defaults: &Defaults, - widget: &dyn Widget<'_, Message, Self>, + widget: &dyn Widget, layout: Layout<'_>, cursor_position: Point, color: Color, diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 0257f819..b8466239 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -134,7 +134,7 @@ impl> Canvas { } } -impl<'a, Message, P, B> Widget<'a, Message, Renderer> for Canvas +impl Widget> for Canvas where P: Program, B: Backend, diff --git a/native/src/element.rs b/native/src/element.rs index 8e0ab295..db95919a 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -17,7 +17,7 @@ use std::rc::Rc; /// [`Element`]: struct.Element.html #[allow(missing_debug_implementations)] pub struct Element<'a, Message, Renderer> { - pub(crate) widget: Box + 'a>, + pub(crate) widget: Box + 'a>, } impl<'a, Message, Renderer> Element<'a, Message, Renderer> @@ -29,7 +29,7 @@ where /// [`Element`]: struct.Element.html /// [`Widget`]: widget/trait.Widget.html pub fn new( - widget: impl Widget<'a, Message, Renderer> + 'a, + widget: impl Widget + 'a, ) -> Element<'a, Message, Renderer> { Element { widget: Box::new(widget), @@ -282,13 +282,13 @@ where } struct Map<'a, A, B, Renderer> { - widget: Box + 'a>, + widget: Box + 'a>, mapper: Rc B>, } impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { pub fn new( - widget: Box + 'a>, + widget: Box + 'a>, mapper: F, ) -> Map<'a, A, B, Renderer> where @@ -301,7 +301,7 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { } } -impl<'a, A, B, Renderer> Widget<'a, B, Renderer> for Map<'a, A, B, Renderer> +impl<'a, A, B, Renderer> Widget for Map<'a, A, B, Renderer> where Renderer: crate::Renderer + 'a, A: 'static, @@ -389,7 +389,7 @@ where } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> +impl<'a, Message, Renderer> Widget for Explain<'a, Message, Renderer> where Renderer: crate::Renderer + layout::Debugger, diff --git a/native/src/layout/debugger.rs b/native/src/layout/debugger.rs index 9c58f4f1..e4b21609 100644 --- a/native/src/layout/debugger.rs +++ b/native/src/layout/debugger.rs @@ -18,7 +18,7 @@ pub trait Debugger: Renderer { fn explain( &mut self, defaults: &Self::Defaults, - widget: &dyn Widget<'_, Message, Self>, + widget: &dyn Widget, layout: Layout<'_>, cursor_position: Point, color: Color, diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 24eaef38..8c5daae1 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -260,7 +260,7 @@ struct List<'a, T, Message, Renderer: self::Renderer> { style: ::Style, } -impl<'a, T, Message, Renderer: self::Renderer> Widget<'a, Message, Renderer> +impl<'a, T, Message, Renderer: self::Renderer> Widget for List<'a, T, Message, Renderer> where T: Clone + ToString, diff --git a/native/src/widget.rs b/native/src/widget.rs index 11952018..a7f279ed 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -97,7 +97,7 @@ use crate::{layout, overlay, Clipboard, Event, Hasher, Layout, Length, Point}; /// [`geometry`]: https://github.com/hecrj/iced/tree/0.1/examples/geometry /// [`lyon`]: https://github.com/nical/lyon /// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.1/wgpu -pub trait Widget<'a, Message, Renderer> +pub trait Widget where Renderer: crate::Renderer, { @@ -179,10 +179,10 @@ where ) { } - fn overlay<'b>( - &'b mut self, + fn overlay( + &mut self, _layout: Layout<'_>, - ) -> Option> { + ) -> Option> { None } } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 72db808b..c932da2b 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -139,7 +139,7 @@ impl State { } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> +impl<'a, Message, Renderer> Widget for Button<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 82fd6d1f..44962288 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -106,7 +106,7 @@ impl } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> +impl Widget for Checkbox where Renderer: self::Renderer + text::Renderer + row::Renderer, diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 83d5054f..42cfe9b9 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -121,7 +121,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> +impl<'a, Message, Renderer> Widget for Column<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index e33eebfd..a9f0e6ed 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -93,7 +93,7 @@ where } } -impl<'a, T: 'a, Message, Renderer> Widget<'a, Message, Renderer> +impl<'a, T: 'a, Message, Renderer> Widget for ComboBox<'a, T, Message, Renderer> where T: Clone + ToString + Eq, diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 0ee67db0..b8316e62 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -129,7 +129,7 @@ where } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> +impl<'a, Message, Renderer> Widget for Container<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index b7c8f4ff..132f249d 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -54,7 +54,7 @@ impl Image { } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Image +impl Widget for Image where Renderer: self::Renderer, { diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index a8e1f852..0b237b9e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -402,7 +402,7 @@ pub struct KeyPressEvent { pub modifiers: keyboard::ModifiersState, } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> +impl<'a, Message, Renderer> Widget for PaneGrid<'a, Message, Renderer> where Renderer: self::Renderer + container::Renderer, diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index 93d86371..5ab76d47 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -70,8 +70,7 @@ impl ProgressBar { } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> - for ProgressBar +impl Widget for ProgressBar where Renderer: self::Renderer, { diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 0d88c740..5b8d00e9 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -121,8 +121,7 @@ impl } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> - for Radio +impl Widget for Radio where Renderer: self::Renderer + text::Renderer + row::Renderer, Message: Clone, diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index eda515b0..2b6db224 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -122,7 +122,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> +impl<'a, Message, Renderer> Widget for Row<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index abd8cfbd..75e97027 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -110,7 +110,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> +impl<'a, Message, Renderer> Widget for Scrollable<'a, Message, Renderer> where Renderer: self::Renderer, diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index e0193342..70f2b6ac 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -154,7 +154,7 @@ impl State { } } -impl<'a, T, Message, Renderer> Widget<'a, Message, Renderer> +impl<'a, T, Message, Renderer> Widget for Slider<'a, T, Message, Renderer> where T: Copy + Into + num_traits::FromPrimitive, diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs index 8ada40ed..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<'a, Message, Renderer> for Space +impl Widget for Space where Renderer: self::Renderer, { diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index 3e45aaf6..114d5e41 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -60,7 +60,7 @@ impl Svg { } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Svg +impl Widget for Svg where Renderer: self::Renderer, { diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 7f75eb9c..48a69e34 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -112,7 +112,7 @@ impl Text { } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> for Text +impl Widget for Text where Renderer: self::Renderer, { diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 63be6019..3f415101 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -165,7 +165,7 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> { } } -impl<'a, Message, Renderer> Widget<'a, Message, Renderer> +impl<'a, Message, Renderer> Widget for TextInput<'a, Message, Renderer> where Renderer: self::Renderer, -- cgit From 2118a726f8b6134820e1ca5b7b802fa1344e453a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Jul 2020 02:39:12 +0200 Subject: Write documentation for the new `overlay` API --- glow/src/lib.rs | 2 +- glow/src/widget/combo_box.rs | 1 + graphics/src/lib.rs | 2 +- graphics/src/overlay.rs | 1 + graphics/src/overlay/menu.rs | 1 + graphics/src/widget/combo_box.rs | 1 + native/src/element.rs | 5 +++- native/src/lib.rs | 4 +-- native/src/overlay.rs | 43 ++++++++++++++++++++++++++++ native/src/overlay/element.rs | 56 +++++++++++++++++++++++++++---------- native/src/overlay/menu.rs | 60 +++++++++++++++++++++++++++++++++++++++- native/src/renderer.rs | 2 ++ native/src/widget.rs | 3 ++ native/src/widget/combo_box.rs | 48 ++++++++++++++++++++++++++++---- wgpu/src/lib.rs | 2 +- wgpu/src/widget/combo_box.rs | 1 + 16 files changed, 205 insertions(+), 27 deletions(-) diff --git a/glow/src/lib.rs b/glow/src/lib.rs index bdd854e3..a6c8a75a 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -2,7 +2,7 @@ //! //! [`glow`]: https://github.com/grovesNL/glow //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(rust_2018_idioms)] diff --git a/glow/src/widget/combo_box.rs b/glow/src/widget/combo_box.rs index bfface29..20feeaca 100644 --- a/glow/src/widget/combo_box.rs +++ b/glow/src/widget/combo_box.rs @@ -1,3 +1,4 @@ +//! Display a dropdown list of selectable values. pub use iced_native::combo_box::State; pub use iced_graphics::combo_box::{Style, StyleSheet}; diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 0c427634..d03f3b48 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -2,7 +2,7 @@ //! for [`iced`]. //! //! [`iced`]: https://github.com/hecrj/iced -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] diff --git a/graphics/src/overlay.rs b/graphics/src/overlay.rs index b9a0e3e0..bc0ed744 100644 --- a/graphics/src/overlay.rs +++ b/graphics/src/overlay.rs @@ -1 +1,2 @@ +//! Display interactive elements on top of other widgets. pub mod menu; diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index 89a9cd03..a952f065 100644 --- a/graphics/src/overlay/menu.rs +++ b/graphics/src/overlay/menu.rs @@ -1,3 +1,4 @@ +//! Build and show dropdown menus. use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{ diff --git a/graphics/src/widget/combo_box.rs b/graphics/src/widget/combo_box.rs index e7ed4e04..f200c2b7 100644 --- a/graphics/src/widget/combo_box.rs +++ b/graphics/src/widget/combo_box.rs @@ -1,3 +1,4 @@ +//! Display a dropdown list of selectable values. use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{ diff --git a/native/src/element.rs b/native/src/element.rs index db95919a..a1320f18 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -24,7 +24,7 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> where Renderer: crate::Renderer, { - /// Create a new [`Element`] containing the given [`Widget`]. + /// Creates a new [`Element`] containing the given [`Widget`]. /// /// [`Element`]: struct.Element.html /// [`Widget`]: widget/trait.Widget.html @@ -273,6 +273,9 @@ where self.widget.hash_layout(state); } + /// Returns the overlay of the [`Element`], if there is any. + /// + /// [`Element`]: struct.Element.html pub fn overlay<'b>( &'b mut self, layout: Layout<'_>, diff --git a/native/src/lib.rs b/native/src/lib.rs index ea328592..067e3c0a 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -30,8 +30,8 @@ //! [`Widget`]: widget/trait.Widget.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -//#![deny(missing_docs)] -//#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] diff --git a/native/src/overlay.rs b/native/src/overlay.rs index b6cbbec3..7c3bec32 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -1,3 +1,4 @@ +//! Display interactive elements on top of other widgets. mod element; pub mod menu; @@ -7,10 +8,19 @@ pub use menu::Menu; use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size}; +/// An interactive component that can be displayed on top of other widgets. pub trait Overlay where Renderer: crate::Renderer, { + /// Returns the layout [`Node`] of the [`Overlay`]. + /// + /// This [`Node`] is used by the runtime to compute the [`Layout`] of the + /// user interface. + /// + /// [`Node`]: ../layout/struct.Node.html + /// [`Widget`]: trait.Overlay.html + /// [`Layout`]: ../layout/struct.Layout.html fn layout( &self, renderer: &Renderer, @@ -18,6 +28,9 @@ where position: Point, ) -> layout::Node; + /// Draws the [`Overlay`] using the associated `Renderer`. + /// + /// [`Overlay`]: trait.Overlay.html fn draw( &self, renderer: &mut Renderer, @@ -26,8 +39,38 @@ where cursor_position: Point, ) -> Renderer::Output; + /// Computes the _layout_ hash of the [`Overlay`]. + /// + /// The produced hash is used by the runtime to decide if the [`Layout`] + /// needs to be recomputed between frames. Therefore, to ensure maximum + /// efficiency, the hash should only be affected by the properties of the + /// [`Overlay`] that can affect layouting. + /// + /// For example, the [`Text`] widget does not hash its color property, as + /// its value cannot affect the overall [`Layout`] of the user interface. + /// + /// [`Overlay`]: trait.Overlay.html + /// [`Layout`]: ../layout/struct.Layout.html + /// [`Text`]: text/struct.Text.html fn hash_layout(&self, state: &mut Hasher, position: Point); + /// Processes a runtime [`Event`]. + /// + /// It receives: + /// * an [`Event`] describing user interaction + /// * the computed [`Layout`] of the [`Overlay`] + /// * the current cursor position + /// * a mutable `Message` list, allowing the [`Overlay`] to produce + /// new messages based on user interaction. + /// * the `Renderer` + /// * a [`Clipboard`], if available + /// + /// By default, it does nothing. + /// + /// [`Event`]: ../enum.Event.html + /// [`Overlay`]: trait.Widget.html + /// [`Layout`]: ../layout/struct.Layout.html + /// [`Clipboard`]: ../trait.Clipboard.html fn on_event( &mut self, _event: Event, diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index a159e3c1..3d532126 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -3,6 +3,9 @@ pub use crate::Overlay; use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size, Vector}; use std::rc::Rc; +/// A generic [`Overlay`]. +/// +/// [`Overlay`]: trait.Overlay.html #[allow(missing_debug_implementations)] pub struct Element<'a, Message, Renderer> { position: Point, @@ -13,6 +16,10 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> where Renderer: crate::Renderer, { + /// Creates a new [`Element`] containing the given [`Overlay`]. + /// + /// [`Element`]: struct.Element.html + /// [`Overlay`]: trait.Overlay.html pub fn new( position: Point, overlay: Box + 'a>, @@ -20,11 +27,17 @@ where Self { position, overlay } } + /// Translates the [`Element`]. + /// + /// [`Element`]: struct.Element.html pub fn translate(mut self, translation: Vector) -> Self { self.position = self.position + translation; self } + /// Applies a transformation to the produced message of the [`Element`]. + /// + /// [`Element`]: struct.Element.html pub fn map(self, f: Rc B>) -> Element<'a, B, Renderer> where Message: 'a, @@ -37,25 +50,16 @@ where } } + /// Computes the layout of the [`Element`] in the given bounds. + /// + /// [`Element`]: struct.Element.html pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node { self.overlay.layout(renderer, bounds, self.position) } - pub fn draw( - &self, - renderer: &mut Renderer, - defaults: &Renderer::Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> Renderer::Output { - self.overlay - .draw(renderer, defaults, layout, cursor_position) - } - - pub fn hash_layout(&self, state: &mut Hasher) { - self.overlay.hash_layout(state, self.position); - } - + /// Processes a runtime [`Event`]. + /// + /// [`Event`]: enum.Event.html pub fn on_event( &mut self, event: Event, @@ -74,6 +78,28 @@ where clipboard, ) } + + /// Draws the [`Element`] and its children using the given [`Layout`]. + /// + /// [`Element`]: struct.Element.html + /// [`Layout`]: layout/struct.Layout.html + pub fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + self.overlay + .draw(renderer, defaults, layout, cursor_position) + } + + /// Computes the _layout_ hash of the [`Element`]. + /// + /// [`Element`]: struct.Element.html + pub fn hash_layout(&self, state: &mut Hasher) { + self.overlay.hash_layout(state, self.position); + } } struct Map<'a, A, B, Renderer> { diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 8c5daae1..0d4bc63c 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -1,9 +1,12 @@ +//! Build and show dropdown menus. use crate::{ container, layout, mouse, overlay, scrollable, text, Clipboard, Container, Element, Event, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size, Vector, Widget, }; +/// A list of selectable options. +#[allow(missing_debug_implementations)] pub struct Menu<'a, T, Message, Renderer: self::Renderer> { state: &'a mut State, options: &'a [T], @@ -21,6 +24,11 @@ where Message: 'a, Renderer: self::Renderer + 'a, { + /// Creates a new [`Menu`] with the given [`State`], a list of options, and + /// the message to produced when an option is selected. + /// + /// [`Menu`]: struct.Menu.html + /// [`State`]: struct.State.html pub fn new( state: &'a mut State, options: &'a [T], @@ -38,26 +46,41 @@ where } } + /// Sets the width of the [`Menu`]. + /// + /// [`Menu`]: struct.Menu.html pub fn width(mut self, width: u16) -> Self { self.width = width; self } + /// Sets the padding of the [`Menu`]. + /// + /// [`Menu`]: struct.Menu.html pub fn padding(mut self, padding: u16) -> Self { self.padding = padding; self } + /// Sets the text size of the [`Menu`]. + /// + /// [`Menu`]: struct.Menu.html pub fn text_size(mut self, text_size: u16) -> Self { self.text_size = Some(text_size); self } + /// Sets the font of the [`Menu`]. + /// + /// [`Menu`]: struct.Menu.html pub fn font(mut self, font: Renderer::Font) -> Self { self.font = font; self } + /// Sets the style of the [`Menu`]. + /// + /// [`Menu`]: struct.Menu.html pub fn style( mut self, style: impl Into<::Style>, @@ -66,6 +89,14 @@ where self } + /// Turns the [`Menu`] into an overlay [`Element`] at the given target + /// position. + /// + /// 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`]. + /// + /// [`Menu`]: struct.Menu.html pub fn overlay( self, position: Point, @@ -78,7 +109,10 @@ where } } -#[derive(Default)] +/// The local state of a [`Menu`]. +/// +/// [`Menu`]: struct.Menu.html +#[derive(Debug, Clone, Default)] pub struct State { scrollable: scrollable::State, hovered_option: Option, @@ -86,10 +120,16 @@ pub struct State { } impl State { + /// Returns whether the [`Menu`] is currently open or not. + /// + /// [`Menu`]: struct.Menu.html pub fn is_open(&self) -> bool { self.is_open } + /// Opens the [`Menu`] with the given option hovered by default. + /// + /// [`Menu`]: struct.Menu.html pub fn open(&mut self, hovered_option: Option) { self.is_open = true; self.hovered_option = hovered_option; @@ -367,11 +407,26 @@ where } } +/// The renderer of a [`Menu`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`Menu`] in your user interface. +/// +/// [`Menu`]: struct.Menu.html +/// [renderer]: ../../renderer/index.html pub trait Renderer: scrollable::Renderer + container::Renderer + text::Renderer { + /// The [`Menu`] style supported by this renderer. + /// + /// [`Menu`]: struct.Menu.html type Style: Default + Clone; + /// Decorates a the list of options of a [`Menu`]. + /// + /// This method can be used to draw a background for the [`Menu`]. + /// + /// [`Menu`]: struct.Menu.html fn decorate( &mut self, bounds: Rectangle, @@ -380,6 +435,9 @@ pub trait Renderer: primitive: Self::Output, ) -> Self::Output; + /// Draws the list of options of a [`Menu`]. + /// + /// [`Menu`]: struct.Menu.html fn draw( &mut self, bounds: Rectangle, diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 29a091a4..d986141f 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -57,6 +57,8 @@ pub trait Renderer: Sized { element.layout(self, limits) } + /// Overlays the `overlay` output with the given bounds on top of the `base` + /// output. fn overlay( &mut self, base: Self::Output, diff --git a/native/src/widget.rs b/native/src/widget.rs index a7f279ed..931b4739 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -179,6 +179,9 @@ where ) { } + /// Returns the overlay of the [`Element`], if there is any. + /// + /// [`Element`]: struct.Element.html fn overlay( &mut self, _layout: Layout<'_>, diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index a9f0e6ed..fefaf8ff 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -1,3 +1,4 @@ +//! Display a dropdown list of selectable values. use crate::{ layout, mouse, overlay, overlay::menu::{self, Menu}, @@ -6,6 +7,8 @@ use crate::{ }; use std::borrow::Cow; +/// A widget for selecting a single value from a list of options. +#[allow(missing_debug_implementations)] pub struct ComboBox<'a, T, Message, Renderer: self::Renderer> where [T]: ToOwned>, @@ -22,7 +25,10 @@ where is_open: bool, } -#[derive(Default)] +/// The local state of a [`ComboBox`]. +/// +/// [`ComboBox`]: struct.ComboBox.html +#[derive(Debug, Clone, Default)] pub struct State { menu: menu::State, } @@ -33,6 +39,12 @@ where T: ToString, [T]: ToOwned>, { + /// Creates a new [`ComboBox`] with the given [`State`], a list of options, + /// the current selected value, and the message to produce when an option is + /// selected. + /// + /// [`ComboBox`]: struct.ComboBox.html + /// [`State`]: struct.State.html pub fn new( state: &'a mut State, options: impl Into>, @@ -57,7 +69,7 @@ where /// Sets the width of the [`ComboBox`]. /// - /// [`ComboBox`]: struct.Button.html + /// [`ComboBox`]: struct.ComboBox.html pub fn width(mut self, width: Length) -> Self { self.width = width; self @@ -65,17 +77,23 @@ where /// Sets the padding of the [`ComboBox`]. /// - /// [`ComboBox`]: struct.Button.html + /// [`ComboBox`]: struct.ComboBox.html pub fn padding(mut self, padding: u16) -> Self { self.padding = padding; self } + /// Sets the text size of the [`ComboBox`]. + /// + /// [`ComboBox`]: struct.ComboBox.html pub fn text_size(mut self, size: u16) -> Self { self.text_size = Some(size); self } + /// Sets the font of the [`ComboBox`]. + /// + /// [`ComboBox`]: struct.ComboBox.html pub fn font(mut self, font: Renderer::Font) -> Self { self.font = font; self @@ -247,15 +265,35 @@ where } } +/// The renderer of a [`ComboBox`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`ComboBox`] in your user interface. +/// +/// [`ComboBox`]: struct.ComboBox.html +/// [renderer]: ../../renderer/index.html pub trait Renderer: text::Renderer + menu::Renderer { - type Style: Default; - + /// The default padding of a [`ComboBox`]. + /// + /// [`ComboBox`]: struct.ComboBox.html const DEFAULT_PADDING: u16; + /// The [`ComboBox`] style supported by this renderer. + /// + /// [`ComboBox`]: struct.ComboBox.html + type Style: Default; + + /// Returns the style of the [`Menu`] of the [`ComboBox`]. + /// + /// [`Menu`]: ../../overlay/menu/struct.Menu.html + /// [`ComboBox`]: struct.ComboBox.html fn menu_style( style: &::Style, ) -> ::Style; + /// Draws a [`ComboBox`]. + /// + /// [`ComboBox`]: struct.ComboBox.html fn draw( &mut self, bounds: Rectangle, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 0186b007..e51a225c 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,7 +20,7 @@ //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/wgpu/src/widget/combo_box.rs b/wgpu/src/widget/combo_box.rs index bfface29..20feeaca 100644 --- a/wgpu/src/widget/combo_box.rs +++ b/wgpu/src/widget/combo_box.rs @@ -1,3 +1,4 @@ +//! Display a dropdown list of selectable values. pub use iced_native::combo_box::State; pub use iced_graphics::combo_box::{Style, StyleSheet}; -- cgit From 73b8ae8e5e7f57c608c775272a2980995ab22bb3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Jul 2020 02:50:47 +0200 Subject: Rename `ComboBox` to `PickList` --- Cargo.toml | 2 +- examples/combo_box/Cargo.toml | 11 -- examples/combo_box/README.md | 18 --- examples/combo_box/src/main.rs | 121 --------------- examples/pick_list/Cargo.toml | 9 ++ examples/pick_list/README.md | 18 +++ examples/pick_list/src/main.rs | 121 +++++++++++++++ glow/src/widget.rs | 6 +- glow/src/widget/combo_box.rs | 9 -- glow/src/widget/pick_list.rs | 9 ++ graphics/src/widget.rs | 4 +- graphics/src/widget/combo_box.rs | 97 ------------ graphics/src/widget/pick_list.rs | 97 ++++++++++++ native/src/widget.rs | 6 +- native/src/widget/combo_box.rs | 320 --------------------------------------- native/src/widget/pick_list.rs | 320 +++++++++++++++++++++++++++++++++++++++ src/widget.rs | 10 +- style/src/combo_box.rs | 70 --------- style/src/lib.rs | 2 +- style/src/pick_list.rs | 70 +++++++++ wgpu/src/widget.rs | 6 +- wgpu/src/widget/combo_box.rs | 9 -- wgpu/src/widget/pick_list.rs | 9 ++ 23 files changed, 672 insertions(+), 672 deletions(-) delete mode 100644 examples/combo_box/Cargo.toml delete mode 100644 examples/combo_box/README.md delete mode 100644 examples/combo_box/src/main.rs create mode 100644 examples/pick_list/Cargo.toml create mode 100644 examples/pick_list/README.md create mode 100644 examples/pick_list/src/main.rs delete mode 100644 glow/src/widget/combo_box.rs create mode 100644 glow/src/widget/pick_list.rs delete mode 100644 graphics/src/widget/combo_box.rs create mode 100644 graphics/src/widget/pick_list.rs delete mode 100644 native/src/widget/combo_box.rs create mode 100644 native/src/widget/pick_list.rs delete mode 100644 style/src/combo_box.rs create mode 100644 style/src/pick_list.rs delete mode 100644 wgpu/src/widget/combo_box.rs create mode 100644 wgpu/src/widget/pick_list.rs diff --git a/Cargo.toml b/Cargo.toml index c62b5c9f..63ccb82e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,6 @@ members = [ "examples/bezier_tool", "examples/clock", "examples/color_palette", - "examples/combo_box", "examples/counter", "examples/custom_widget", "examples/download_progress", @@ -65,6 +64,7 @@ members = [ "examples/geometry", "examples/integration", "examples/pane_grid", + "examples/pick_list", "examples/pokedex", "examples/progress_bar", "examples/solar_system", diff --git a/examples/combo_box/Cargo.toml b/examples/combo_box/Cargo.toml deleted file mode 100644 index 7e1e4133..00000000 --- a/examples/combo_box/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "combo_box" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2018" -publish = false - -[dependencies] -iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } -iced_wgpu = { path = "../../wgpu" } diff --git a/examples/combo_box/README.md b/examples/combo_box/README.md deleted file mode 100644 index 4d9fc5b9..00000000 --- a/examples/combo_box/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Counter - -The classic counter example explained in the [`README`](../../README.md). - -The __[`main`]__ file contains all the code of the example. - - - -You can run it with `cargo run`: -``` -cargo run --package counter -``` - -[`main`]: src/main.rs diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs deleted file mode 100644 index 416e9f76..00000000 --- a/examples/combo_box/src/main.rs +++ /dev/null @@ -1,121 +0,0 @@ -use iced::{ - button, combo_box, scrollable, Align, Button, ComboBox, Container, Element, - Length, Sandbox, Scrollable, Settings, Space, Text, -}; - -pub fn main() { - Example::run(Settings::default()) -} - -#[derive(Default)] -struct Example { - scroll: scrollable::State, - button: button::State, - combo_box: combo_box::State, - selected_language: Language, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - ButtonPressed, - LanguageSelected(Language), -} - -impl Sandbox for Example { - type Message = Message; - - fn new() -> Self { - Self::default() - } - - fn title(&self) -> String { - String::from("Combo box - Iced") - } - - fn update(&mut self, message: Message) { - match message { - Message::ButtonPressed => {} - Message::LanguageSelected(language) => { - self.selected_language = language; - } - } - } - - fn view(&mut self) -> Element { - let combo_box = ComboBox::new( - &mut self.combo_box, - &Language::ALL[..], - Some(self.selected_language), - Message::LanguageSelected, - ); - - let button = Button::new(&mut self.button, Text::new("Press me!")) - .on_press(Message::ButtonPressed); - - let mut content = Scrollable::new(&mut self.scroll) - .width(Length::Fill) - .align_items(Align::Center) - .spacing(10) - .push(Space::with_height(Length::Units(800))) - .push(Text::new("Which is your favorite language?")) - .push(combo_box); - - content = content - .push(button) - .push(Space::with_height(Length::Units(800))); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Language { - Rust, - Elm, - Ruby, - Haskell, - C, - Javascript, - Other, -} - -impl Language { - const ALL: [Language; 7] = [ - Language::C, - Language::Elm, - Language::Ruby, - Language::Haskell, - Language::Rust, - Language::Javascript, - Language::Other, - ]; -} - -impl Default for Language { - fn default() -> Language { - Language::Rust - } -} - -impl std::fmt::Display for Language { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Language::Rust => "Rust", - Language::Elm => "Elm", - Language::Ruby => "Ruby", - Language::Haskell => "Haskell", - Language::C => "C", - Language::Javascript => "Javascript", - Language::Other => "Some other language", - } - ) - } -} diff --git a/examples/pick_list/Cargo.toml b/examples/pick_list/Cargo.toml new file mode 100644 index 00000000..a87d7217 --- /dev/null +++ b/examples/pick_list/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pick_list" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } diff --git a/examples/pick_list/README.md b/examples/pick_list/README.md new file mode 100644 index 00000000..4d9fc5b9 --- /dev/null +++ b/examples/pick_list/README.md @@ -0,0 +1,18 @@ +## Counter + +The classic counter example explained in the [`README`](../../README.md). + +The __[`main`]__ file contains all the code of the example. + + + +You can run it with `cargo run`: +``` +cargo run --package counter +``` + +[`main`]: src/main.rs diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs new file mode 100644 index 00000000..4eb368d1 --- /dev/null +++ b/examples/pick_list/src/main.rs @@ -0,0 +1,121 @@ +use iced::{ + button, pick_list, scrollable, Align, Button, Container, Element, Length, + PickList, Sandbox, Scrollable, Settings, Space, Text, +}; + +pub fn main() { + Example::run(Settings::default()) +} + +#[derive(Default)] +struct Example { + scroll: scrollable::State, + button: button::State, + pick_list: pick_list::State, + selected_language: Language, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + ButtonPressed, + LanguageSelected(Language), +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Pick list - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::ButtonPressed => {} + Message::LanguageSelected(language) => { + self.selected_language = language; + } + } + } + + fn view(&mut self) -> Element { + let pick_list = PickList::new( + &mut self.pick_list, + &Language::ALL[..], + Some(self.selected_language), + Message::LanguageSelected, + ); + + let button = Button::new(&mut self.button, Text::new("Press me!")) + .on_press(Message::ButtonPressed); + + let mut content = Scrollable::new(&mut self.scroll) + .width(Length::Fill) + .align_items(Align::Center) + .spacing(10) + .push(Space::with_height(Length::Units(800))) + .push(Text::new("Which is your favorite language?")) + .push(pick_list); + + content = content + .push(button) + .push(Space::with_height(Length::Units(800))); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Language { + Rust, + Elm, + Ruby, + Haskell, + C, + Javascript, + Other, +} + +impl Language { + const ALL: [Language; 7] = [ + Language::C, + Language::Elm, + Language::Ruby, + Language::Haskell, + Language::Rust, + Language::Javascript, + Language::Other, + ]; +} + +impl Default for Language { + fn default() -> Language { + Language::Rust + } +} + +impl std::fmt::Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Language::Rust => "Rust", + Language::Elm => "Elm", + Language::Ruby => "Ruby", + Language::Haskell => "Haskell", + Language::C => "C", + Language::Javascript => "Javascript", + Language::Other => "Some other language", + } + ) + } +} diff --git a/glow/src/widget.rs b/glow/src/widget.rs index c8f16725..4e2fedc5 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -11,9 +11,9 @@ use crate::Renderer; pub mod button; pub mod checkbox; -pub mod combo_box; pub mod container; pub mod pane_grid; +pub mod pick_list; pub mod progress_bar; pub mod radio; pub mod scrollable; @@ -25,12 +25,12 @@ pub use button::Button; #[doc(no_inline)] pub use checkbox::Checkbox; #[doc(no_inline)] -pub use combo_box::ComboBox; -#[doc(no_inline)] pub use container::Container; #[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/glow/src/widget/combo_box.rs b/glow/src/widget/combo_box.rs deleted file mode 100644 index 20feeaca..00000000 --- a/glow/src/widget/combo_box.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Display a dropdown list of selectable values. -pub use iced_native::combo_box::State; - -pub use iced_graphics::combo_box::{Style, StyleSheet}; -pub use iced_graphics::overlay::menu::Style as Menu; - -/// A widget allowing the selection of a single value from a list of options. -pub type ComboBox<'a, T, Message> = - iced_native::ComboBox<'a, T, Message, crate::Renderer>; diff --git a/glow/src/widget/pick_list.rs b/glow/src/widget/pick_list.rs new file mode 100644 index 00000000..fccc68c9 --- /dev/null +++ b/glow/src/widget/pick_list.rs @@ -0,0 +1,9 @@ +//! Display a dropdown list of selectable values. +pub use iced_native::pick_list::State; + +pub use iced_graphics::overlay::menu::Style as Menu; +pub use iced_graphics::pick_list::{Style, StyleSheet}; + +/// A widget allowing the selection of a single value from a list of options. +pub type PickList<'a, T, Message> = + iced_native::PickList<'a, T, Message, crate::Renderer>; diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index a0d06999..94a65011 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -9,10 +9,10 @@ //! ``` pub mod button; pub mod checkbox; -pub mod combo_box; pub mod container; pub mod image; pub mod pane_grid; +pub mod pick_list; pub mod progress_bar; pub mod radio; pub mod scrollable; @@ -34,6 +34,8 @@ pub use container::Container; #[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/graphics/src/widget/combo_box.rs b/graphics/src/widget/combo_box.rs deleted file mode 100644 index f200c2b7..00000000 --- a/graphics/src/widget/combo_box.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Display a dropdown list of selectable values. -use crate::backend::{self, Backend}; -use crate::{Primitive, Renderer}; -use iced_native::{ - mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment, -}; -use iced_style::menu; - -pub use iced_native::combo_box::State; -pub use iced_style::combo_box::{Style, StyleSheet}; - -/// A widget allowing the selection of a single value from a list of options. -pub type ComboBox<'a, T, Message, Backend> = - iced_native::ComboBox<'a, T, Message, Renderer>; - -impl iced_native::combo_box::Renderer for Renderer -where - B: Backend + backend::Text, -{ - type Style = Box; - - const DEFAULT_PADDING: u16 = 5; - - fn menu_style(style: &Box) -> menu::Style { - style.menu() - } - - fn draw( - &mut self, - bounds: Rectangle, - cursor_position: Point, - selected: Option, - padding: u16, - text_size: u16, - font: Font, - style: &Box, - ) -> Self::Output { - let is_mouse_over = bounds.contains(cursor_position); - - let style = if is_mouse_over { - style.hovered() - } else { - style.active() - }; - - let background = Primitive::Quad { - bounds, - background: style.background, - border_color: style.border_color, - border_width: style.border_width, - border_radius: style.border_radius, - }; - - let arrow_down = Primitive::Text { - content: B::ARROW_DOWN_ICON.to_string(), - font: B::ICON_FONT, - size: bounds.height * style.icon_size, - bounds: Rectangle { - x: bounds.x + bounds.width - f32::from(padding) * 2.0, - y: bounds.center_y(), - ..bounds - }, - color: style.text_color, - horizontal_alignment: HorizontalAlignment::Right, - vertical_alignment: VerticalAlignment::Center, - }; - - ( - Primitive::Group { - primitives: if let Some(label) = selected { - let label = Primitive::Text { - content: label, - size: f32::from(text_size), - font, - color: style.text_color, - bounds: Rectangle { - x: bounds.x + f32::from(padding), - y: bounds.center_y(), - ..bounds - }, - horizontal_alignment: HorizontalAlignment::Left, - vertical_alignment: VerticalAlignment::Center, - }; - - vec![background, label, arrow_down] - } else { - vec![background, arrow_down] - }, - }, - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs new file mode 100644 index 00000000..f42a8707 --- /dev/null +++ b/graphics/src/widget/pick_list.rs @@ -0,0 +1,97 @@ +//! Display a dropdown list of selectable values. +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::{ + mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment, +}; +use iced_style::menu; + +pub use iced_native::pick_list::State; +pub use iced_style::pick_list::{Style, StyleSheet}; + +/// A widget allowing the selection of a single value from a list of options. +pub type PickList<'a, T, Message, Backend> = + iced_native::PickList<'a, T, Message, Renderer>; + +impl iced_native::pick_list::Renderer for Renderer +where + B: Backend + backend::Text, +{ + type Style = Box; + + const DEFAULT_PADDING: u16 = 5; + + fn menu_style(style: &Box) -> menu::Style { + style.menu() + } + + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + selected: Option, + padding: u16, + text_size: u16, + font: Font, + style: &Box, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let style = if is_mouse_over { + style.hovered() + } else { + style.active() + }; + + let background = Primitive::Quad { + bounds, + background: style.background, + border_color: style.border_color, + border_width: style.border_width, + border_radius: style.border_radius, + }; + + let arrow_down = Primitive::Text { + content: B::ARROW_DOWN_ICON.to_string(), + font: B::ICON_FONT, + size: bounds.height * style.icon_size, + bounds: Rectangle { + x: bounds.x + bounds.width - f32::from(padding) * 2.0, + y: bounds.center_y(), + ..bounds + }, + color: style.text_color, + horizontal_alignment: HorizontalAlignment::Right, + vertical_alignment: VerticalAlignment::Center, + }; + + ( + Primitive::Group { + primitives: if let Some(label) = selected { + let label = Primitive::Text { + content: label, + size: f32::from(text_size), + font, + color: style.text_color, + bounds: Rectangle { + x: bounds.x + f32::from(padding), + y: bounds.center_y(), + ..bounds + }, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Center, + }; + + vec![background, label, arrow_down] + } else { + vec![background, arrow_down] + }, + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/native/src/widget.rs b/native/src/widget.rs index 931b4739..8539e519 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -23,10 +23,10 @@ pub mod button; pub mod checkbox; pub mod column; -pub mod combo_box; pub mod container; pub mod image; pub mod pane_grid; +pub mod pick_list; pub mod progress_bar; pub mod radio; pub mod row; @@ -44,14 +44,14 @@ pub use checkbox::Checkbox; #[doc(no_inline)] pub use column::Column; #[doc(no_inline)] -pub use combo_box::ComboBox; -#[doc(no_inline)] pub use container::Container; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs deleted file mode 100644 index fefaf8ff..00000000 --- a/native/src/widget/combo_box.rs +++ /dev/null @@ -1,320 +0,0 @@ -//! 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 ComboBox<'a, T, Message, Renderer: self::Renderer> -where - [T]: ToOwned>, -{ - menu: &'a mut menu::State, - on_selected: Box Message>, - options: Cow<'a, [T]>, - selected: Option, - width: Length, - padding: u16, - text_size: Option, - font: Renderer::Font, - style: ::Style, - is_open: bool, -} - -/// The local state of a [`ComboBox`]. -/// -/// [`ComboBox`]: struct.ComboBox.html -#[derive(Debug, Clone, Default)] -pub struct State { - menu: menu::State, -} - -impl<'a, T: 'a, Message, Renderer: self::Renderer> - ComboBox<'a, T, Message, Renderer> -where - T: ToString, - [T]: ToOwned>, -{ - /// Creates a new [`ComboBox`] with the given [`State`], a list of options, - /// the current selected value, and the message to produce when an option is - /// selected. - /// - /// [`ComboBox`]: struct.ComboBox.html - /// [`State`]: struct.State.html - pub fn new( - state: &'a mut State, - options: impl Into>, - selected: Option, - 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 [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the padding of the [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - pub fn padding(mut self, padding: u16) -> Self { - self.padding = padding; - self - } - - /// Sets the text size of the [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - pub fn text_size(mut self, size: u16) -> Self { - self.text_size = Some(size); - self - } - - /// Sets the font of the [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; - self - } - - /// Sets the style of the [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, T: 'a, Message, Renderer> Widget - for ComboBox<'a, T, Message, Renderer> -where - T: Clone + ToString + Eq, - [T]: ToOwned>, - 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, - _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> { - 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 [`ComboBox`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`ComboBox`] in your user interface. -/// -/// [`ComboBox`]: struct.ComboBox.html -/// [renderer]: ../../renderer/index.html -pub trait Renderer: text::Renderer + menu::Renderer { - /// The default padding of a [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - const DEFAULT_PADDING: u16; - - /// The [`ComboBox`] style supported by this renderer. - /// - /// [`ComboBox`]: struct.ComboBox.html - type Style: Default; - - /// Returns the style of the [`Menu`] of the [`ComboBox`]. - /// - /// [`Menu`]: ../../overlay/menu/struct.Menu.html - /// [`ComboBox`]: struct.ComboBox.html - fn menu_style( - style: &::Style, - ) -> ::Style; - - /// Draws a [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - fn draw( - &mut self, - bounds: Rectangle, - cursor_position: Point, - selected: Option, - padding: u16, - text_size: u16, - font: Self::Font, - style: &::Style, - ) -> Self::Output; -} - -impl<'a, T: 'a, Message, Renderer> Into> - for ComboBox<'a, T, Message, Renderer> -where - T: Clone + ToString + Eq, - [T]: ToOwned>, - Renderer: self::Renderer + 'a, - Message: 'static, -{ - fn into(self) -> Element<'a, Message, Renderer> { - Element::new(self) - } -} 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>, +{ + menu: &'a mut menu::State, + on_selected: Box Message>, + options: Cow<'a, [T]>, + selected: Option, + width: Length, + padding: u16, + text_size: Option, + font: Renderer::Font, + style: ::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>, +{ + /// 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>, + selected: Option, + 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<::Style>, + ) -> Self { + self.style = style.into(); + self + } +} + +impl<'a, T: 'a, Message, Renderer> Widget + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq, + [T]: ToOwned>, + 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, + _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> { + 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: &::Style, + ) -> ::Style; + + /// Draws a [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + selected: Option, + padding: u16, + text_size: u16, + font: Self::Font, + style: &::Style, + ) -> Self::Output; +} + +impl<'a, T: 'a, Message, Renderer> Into> + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq, + [T]: ToOwned>, + Renderer: self::Renderer + 'a, + Message: 'static, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} diff --git a/src/widget.rs b/src/widget.rs index 034f02cd..b26f14d4 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -19,7 +19,7 @@ #[cfg(not(target_arch = "wasm32"))] mod platform { pub use crate::renderer::widget::{ - button, checkbox, combo_box, container, pane_grid, progress_bar, radio, + button, checkbox, container, pane_grid, pick_list, progress_bar, radio, scrollable, slider, text_input, Column, Row, Space, Text, }; @@ -44,10 +44,10 @@ mod platform { #[doc(no_inline)] pub use { - button::Button, checkbox::Checkbox, combo_box::ComboBox, - container::Container, image::Image, pane_grid::PaneGrid, - progress_bar::ProgressBar, radio::Radio, scrollable::Scrollable, - slider::Slider, svg::Svg, text_input::TextInput, + button::Button, checkbox::Checkbox, container::Container, image::Image, + pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, + radio::Radio, scrollable::Scrollable, slider::Slider, svg::Svg, + text_input::TextInput, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] diff --git a/style/src/combo_box.rs b/style/src/combo_box.rs deleted file mode 100644 index 4d0c4e46..00000000 --- a/style/src/combo_box.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::menu; -use iced_core::{Background, Color}; - -/// The appearance of a combo box. -#[derive(Debug, Clone, Copy)] -pub struct Style { - pub text_color: Color, - pub background: Background, - pub border_radius: u16, - pub border_width: u16, - pub border_color: Color, - pub icon_size: f32, -} - -impl std::default::Default for Style { - fn default() -> Self { - Self { - text_color: Color::BLACK, - background: Background::Color([0.87, 0.87, 0.87].into()), - border_radius: 0, - border_width: 1, - border_color: [0.7, 0.7, 0.7].into(), - icon_size: 0.7, - } - } -} - -/// A set of rules that dictate the style of a container. -pub trait StyleSheet { - fn menu(&self) -> menu::Style; - - fn active(&self) -> Style; - - /// Produces the style of a container. - fn hovered(&self) -> Style; -} - -struct Default; - -impl StyleSheet for Default { - fn menu(&self) -> menu::Style { - menu::Style::default() - } - - fn active(&self) -> Style { - Style::default() - } - - fn hovered(&self) -> Style { - Style { - border_color: Color::BLACK, - ..self.active() - } - } -} - -impl std::default::Default for Box { - fn default() -> Self { - Box::new(Default) - } -} - -impl From for Box -where - T: 'static + StyleSheet, -{ - fn from(style: T) -> Self { - Box::new(style) - } -} diff --git a/style/src/lib.rs b/style/src/lib.rs index b19d6600..8e402bb1 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -6,9 +6,9 @@ pub use iced_core::{Background, Color}; pub mod button; pub mod checkbox; -pub mod combo_box; pub mod container; pub mod menu; +pub mod pick_list; pub mod progress_bar; pub mod radio; pub mod scrollable; diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs new file mode 100644 index 00000000..fbd431c0 --- /dev/null +++ b/style/src/pick_list.rs @@ -0,0 +1,70 @@ +use crate::menu; +use iced_core::{Background, Color}; + +/// The appearance of a pick list. +#[derive(Debug, Clone, Copy)] +pub struct Style { + pub text_color: Color, + pub background: Background, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, + pub icon_size: f32, +} + +impl std::default::Default for Style { + fn default() -> Self { + Self { + text_color: Color::BLACK, + background: Background::Color([0.87, 0.87, 0.87].into()), + border_radius: 0, + border_width: 1, + border_color: [0.7, 0.7, 0.7].into(), + icon_size: 0.7, + } + } +} + +/// A set of rules that dictate the style of a container. +pub trait StyleSheet { + fn menu(&self) -> menu::Style; + + fn active(&self) -> Style; + + /// Produces the style of a container. + fn hovered(&self) -> Style; +} + +struct Default; + +impl StyleSheet for Default { + fn menu(&self) -> menu::Style { + menu::Style::default() + } + + fn active(&self) -> Style { + Style::default() + } + + fn hovered(&self) -> Style { + Style { + border_color: Color::BLACK, + ..self.active() + } + } +} + +impl std::default::Default for Box { + fn default() -> Self { + Box::new(Default) + } +} + +impl From for Box +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 0f390c8d..ced64332 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -11,9 +11,9 @@ use crate::Renderer; pub mod button; pub mod checkbox; -pub mod combo_box; pub mod container; pub mod pane_grid; +pub mod pick_list; pub mod progress_bar; pub mod radio; pub mod scrollable; @@ -25,12 +25,12 @@ pub use button::Button; #[doc(no_inline)] pub use checkbox::Checkbox; #[doc(no_inline)] -pub use combo_box::ComboBox; -#[doc(no_inline)] pub use container::Container; #[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/wgpu/src/widget/combo_box.rs b/wgpu/src/widget/combo_box.rs deleted file mode 100644 index 20feeaca..00000000 --- a/wgpu/src/widget/combo_box.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Display a dropdown list of selectable values. -pub use iced_native::combo_box::State; - -pub use iced_graphics::combo_box::{Style, StyleSheet}; -pub use iced_graphics::overlay::menu::Style as Menu; - -/// A widget allowing the selection of a single value from a list of options. -pub type ComboBox<'a, T, Message> = - iced_native::ComboBox<'a, T, Message, crate::Renderer>; diff --git a/wgpu/src/widget/pick_list.rs b/wgpu/src/widget/pick_list.rs new file mode 100644 index 00000000..fccc68c9 --- /dev/null +++ b/wgpu/src/widget/pick_list.rs @@ -0,0 +1,9 @@ +//! Display a dropdown list of selectable values. +pub use iced_native::pick_list::State; + +pub use iced_graphics::overlay::menu::Style as Menu; +pub use iced_graphics::pick_list::{Style, StyleSheet}; + +/// A widget allowing the selection of a single value from a list of options. +pub type PickList<'a, T, Message> = + iced_native::PickList<'a, T, Message, crate::Renderer>; -- cgit From b64e0ea5e31e718e435b24244119f59f7128bc27 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Jul 2020 04:14:21 +0200 Subject: Add `Preset` selector to `game_of_life` example --- examples/game_of_life/src/main.rs | 79 +++++++++++++++++--- examples/game_of_life/src/preset.rs | 142 ++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 examples/game_of_life/src/preset.rs diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 080d55c0..4663ae72 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -1,15 +1,19 @@ //! This example showcases an interactive version of the Game of Life, invented //! by John Conway. It leverages a `Canvas` together with other widgets. +mod preset; mod style; use grid::Grid; +use iced::button::{self, Button}; +use iced::executor; +use iced::pick_list::{self, PickList}; +use iced::slider::{self, Slider}; +use iced::time; use iced::{ - button::{self, Button}, - executor, - slider::{self, Slider}, - time, Align, Application, Checkbox, Column, Command, Container, Element, - Length, Row, Settings, Subscription, Text, + Align, Application, Checkbox, Column, Command, Container, Element, Length, + Row, Settings, Subscription, Text, }; +use preset::Preset; use std::time::{Duration, Instant}; pub fn main() { @@ -38,6 +42,7 @@ enum Message { Next, Clear, SpeedChanged(f32), + PresetPicked(Preset), } impl Application for GameOfLife { @@ -48,7 +53,7 @@ impl Application for GameOfLife { fn new(_flags: ()) -> (Self, Command) { ( Self { - speed: 1, + speed: 5, ..Self::default() }, Command::none(), @@ -93,6 +98,9 @@ impl Application for GameOfLife { self.speed = speed.round() as usize; } } + Message::PresetPicked(new_preset) => { + self.grid = Grid::from_preset(new_preset); + } } Command::none() @@ -113,6 +121,7 @@ impl Application for GameOfLife { self.is_playing, self.grid.are_lines_visible(), selected_speed, + self.grid.preset(), ); let content = Column::new() @@ -128,6 +137,7 @@ impl Application for GameOfLife { } mod grid { + use crate::Preset; use iced::{ canvas::{ self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path, Text, @@ -142,6 +152,7 @@ mod grid { pub struct Grid { state: State, + preset: Preset, interaction: Interaction, life_cache: Cache, grid_cache: Cache, @@ -171,8 +182,24 @@ mod grid { impl Default for Grid { fn default() -> Self { + Self::from_preset(Preset::default()) + } + } + + impl Grid { + const MIN_SCALING: f32 = 0.1; + const MAX_SCALING: f32 = 2.0; + + pub fn from_preset(preset: Preset) -> Self { Self { - state: State::default(), + state: State::with_life( + preset + .life() + .into_iter() + .map(|(i, j)| Cell { i, j }) + .collect(), + ), + preset, interaction: Interaction::None, life_cache: Cache::default(), grid_cache: Cache::default(), @@ -184,11 +211,6 @@ mod grid { version: 0, } } - } - - impl Grid { - const MIN_SCALING: f32 = 0.1; - const MAX_SCALING: f32 = 2.0; pub fn tick( &mut self, @@ -217,10 +239,14 @@ mod grid { Message::Populate(cell) => { self.state.populate(cell); self.life_cache.clear(); + + self.preset = Preset::Custom; } Message::Unpopulate(cell) => { self.state.unpopulate(&cell); self.life_cache.clear(); + + self.preset = Preset::Custom; } Message::Ticked { result: Ok(life), @@ -230,6 +256,7 @@ mod grid { self.state.update(life); self.life_cache.clear(); + self.version += 1; self.last_tick_duration = tick_duration; } Message::Ticked { @@ -250,11 +277,16 @@ mod grid { pub fn clear(&mut self) { self.state = State::default(); + self.preset = Preset::Custom; self.version += 1; self.life_cache.clear(); } + pub fn preset(&self) -> Preset { + self.preset + } + pub fn toggle_lines(&mut self, enabled: bool) { self.show_lines = enabled; } @@ -533,6 +565,13 @@ mod grid { } impl State { + pub fn with_life(life: Life) -> Self { + Self { + life, + ..Self::default() + } + } + fn cell_count(&self) -> usize { self.life.len() + self.births.len() } @@ -647,6 +686,14 @@ mod grid { } } + impl std::iter::FromIterator for Life { + fn from_iter>(iter: I) -> Self { + Life { + cells: iter.into_iter().collect(), + } + } + } + impl std::fmt::Debug for Life { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Life") @@ -741,6 +788,7 @@ struct Controls { next_button: button::State, clear_button: button::State, speed_slider: slider::State, + preset_list: pick_list::State, } impl Controls { @@ -749,6 +797,7 @@ impl Controls { is_playing: bool, is_grid_enabled: bool, speed: usize, + preset: Preset, ) -> Element<'a, Message> { let playback_controls = Row::new() .spacing(10) @@ -793,6 +842,12 @@ impl Controls { .spacing(5) .text_size(16), ) + .push(PickList::new( + &mut self.preset_list, + preset::ALL, + Some(preset), + Message::PresetPicked, + )) .push( Button::new(&mut self.clear_button, Text::new("Clear")) .on_press(Message::Clear) diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs new file mode 100644 index 00000000..1a471141 --- /dev/null +++ b/examples/game_of_life/src/preset.rs @@ -0,0 +1,142 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Preset { + Custom, + XKCD, + Glider, + SmallExploder, + Exploder, + TenCellRow, + LightweightSpaceship, + Tumbler, + Acorn, + GliderGun, +} + +pub static ALL: &[Preset] = &[ + Preset::Custom, + Preset::XKCD, + Preset::Glider, + Preset::SmallExploder, + Preset::Exploder, + Preset::TenCellRow, + Preset::LightweightSpaceship, + Preset::Tumbler, + Preset::Acorn, + Preset::GliderGun, +]; + +impl Preset { + pub fn life(self) -> Vec<(isize, isize)> { + #[rustfmt::skip] + let cells = match self { + Preset::Custom => vec![], + Preset::XKCD => vec![ + " xxx ", + " x x ", + " x x ", + " x ", + "x xxx ", + " x x x ", + " x x", + " x x ", + " x x ", + ], + Preset::Glider => vec![ + " x ", + " x", + "xxx" + ], + Preset::SmallExploder => vec![ + " x ", + "xxx", + "x x", + " x ", + ], + Preset::Exploder => vec![ + "x x x", + "x x", + "x x", + "x x", + "x x x", + ], + Preset::TenCellRow => vec![ + "xxxxxxxxxx", + ], + Preset::LightweightSpaceship => vec![ + " xxxxx", + "x x", + " x", + "x x ", + ], + Preset::Tumbler => vec![ + " xx xx ", + " xx xx ", + " x x ", + "x x x x", + "x x x x", + "xx xx", + ], + Preset::Acorn => vec![ + " x ", + " x ", + "xx xxx", + ], + Preset::GliderGun => vec![ + " x ", + " x x ", + " xx xx xx", + " x x xx xx", + "xx x x xx ", + "xx x x xx x x ", + " x x x ", + " x x ", + " xx ", + ] + }; + + let start_row = -(cells.len() as isize / 2); + + cells + .into_iter() + .enumerate() + .flat_map(|(i, cells)| { + let start_column = -(cells.len() as isize / 2); + + cells + .chars() + .enumerate() + .filter(|(_, c)| !c.is_whitespace()) + .map(move |(j, _)| { + (start_row + i as isize, start_column + j as isize) + }) + }) + .collect() + } +} + +impl Default for Preset { + fn default() -> Preset { + Preset::XKCD + } +} + +impl std::fmt::Display for Preset { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Preset::Custom => "Custom", + Preset::XKCD => "xkcd #2293", + Preset::Glider => "Glider", + Preset::SmallExploder => "Small Exploder", + Preset::Exploder => "Exploder", + Preset::TenCellRow => "10 Cell Row", + Preset::LightweightSpaceship => "Lightweight spaceship", + Preset::Tumbler => "Tumbler", + Preset::Acorn => "Acorn", + Preset::GliderGun => "Gosper Glider Gun", + } + ) + } +} -- cgit From 94383d82a5d080a28de025fadc6b7ba27e37927d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Jul 2020 07:41:31 +0200 Subject: Style `PickList` in `game_of_life` example --- examples/game_of_life/src/main.rs | 17 +++++++---- examples/game_of_life/src/preset.rs | 18 ++++++------ examples/game_of_life/src/style.rs | 56 ++++++++++++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 4663ae72..27d4eec1 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -842,12 +842,17 @@ impl Controls { .spacing(5) .text_size(16), ) - .push(PickList::new( - &mut self.preset_list, - preset::ALL, - Some(preset), - Message::PresetPicked, - )) + .push( + PickList::new( + &mut self.preset_list, + preset::ALL, + Some(preset), + Message::PresetPicked, + ) + .padding(8) + .text_size(16) + .style(style::PickList), + ) .push( Button::new(&mut self.clear_button, Text::new("Clear")) .on_press(Message::Clear) diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs index 1a471141..05157b6a 100644 --- a/examples/game_of_life/src/preset.rs +++ b/examples/game_of_life/src/preset.rs @@ -8,8 +8,8 @@ pub enum Preset { TenCellRow, LightweightSpaceship, Tumbler, - Acorn, GliderGun, + Acorn, } pub static ALL: &[Preset] = &[ @@ -21,8 +21,8 @@ pub static ALL: &[Preset] = &[ Preset::TenCellRow, Preset::LightweightSpaceship, Preset::Tumbler, - Preset::Acorn, Preset::GliderGun, + Preset::Acorn, ]; impl Preset { @@ -76,11 +76,6 @@ impl Preset { "x x x x", "xx xx", ], - Preset::Acorn => vec![ - " x ", - " x ", - "xx xxx", - ], Preset::GliderGun => vec![ " x ", " x x ", @@ -91,7 +86,12 @@ impl Preset { " x x x ", " x x ", " xx ", - ] + ], + Preset::Acorn => vec![ + " x ", + " x ", + "xx xxx", + ], }; let start_row = -(cells.len() as isize / 2); @@ -134,8 +134,8 @@ impl std::fmt::Display for Preset { Preset::TenCellRow => "10 Cell Row", Preset::LightweightSpaceship => "Lightweight spaceship", Preset::Tumbler => "Tumbler", - Preset::Acorn => "Acorn", Preset::GliderGun => "Gosper Glider Gun", + Preset::Acorn => "Acorn", } ) } diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs index d59569f2..308ce43c 100644 --- a/examples/game_of_life/src/style.rs +++ b/examples/game_of_life/src/style.rs @@ -1,4 +1,4 @@ -use iced::{button, container, slider, Background, Color}; +use iced::{button, container, pick_list, slider, Background, Color}; const ACTIVE: Color = Color::from_rgb( 0x72 as f32 / 255.0, @@ -18,6 +18,12 @@ const HOVERED: Color = Color::from_rgb( 0xC4 as f32 / 255.0, ); +const BACKGROUND: Color = Color::from_rgb( + 0x2F as f32 / 255.0, + 0x31 as f32 / 255.0, + 0x36 as f32 / 255.0, +); + pub struct Container; impl container::StyleSheet for Container { @@ -132,3 +138,51 @@ impl slider::StyleSheet for Slider { } } } + +pub struct PickList; + +impl pick_list::StyleSheet for PickList { + fn menu(&self) -> pick_list::Menu { + pick_list::Menu { + text_color: Color::WHITE, + background: BACKGROUND.into(), + border_width: 1, + border_color: Color { + a: 0.7, + ..Color::BLACK + }, + selected_background: Color { + a: 0.5, + ..Color::BLACK + } + .into(), + selected_text_color: Color::WHITE, + } + } + + fn active(&self) -> pick_list::Style { + pick_list::Style { + text_color: Color::WHITE, + background: BACKGROUND.into(), + border_width: 1, + border_color: Color { + a: 0.6, + ..Color::BLACK + }, + border_radius: 2, + icon_size: 0.5, + } + } + + fn hovered(&self) -> pick_list::Style { + let active = self.active(); + + pick_list::Style { + border_color: Color { + a: 0.9, + ..Color::BLACK + }, + ..active + } + } +} -- cgit From 26fbc9af0097f7f912c554b3a1e0281678bb4823 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Jul 2020 08:11:31 +0200 Subject: Simplify `pick_list` example --- examples/pick_list/src/main.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs index 4eb368d1..66ed6c6f 100644 --- a/examples/pick_list/src/main.rs +++ b/examples/pick_list/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, pick_list, scrollable, Align, Button, Container, Element, Length, - PickList, Sandbox, Scrollable, Settings, Space, Text, + pick_list, scrollable, Align, Container, Element, Length, PickList, + Sandbox, Scrollable, Settings, Space, Text, }; pub fn main() { @@ -10,14 +10,12 @@ pub fn main() { #[derive(Default)] struct Example { scroll: scrollable::State, - button: button::State, pick_list: pick_list::State, selected_language: Language, } #[derive(Debug, Clone, Copy)] enum Message { - ButtonPressed, LanguageSelected(Language), } @@ -34,7 +32,6 @@ impl Sandbox for Example { fn update(&mut self, message: Message) { match message { - Message::ButtonPressed => {} Message::LanguageSelected(language) => { self.selected_language = language; } @@ -49,20 +46,15 @@ impl Sandbox for Example { Message::LanguageSelected, ); - let button = Button::new(&mut self.button, Text::new("Press me!")) - .on_press(Message::ButtonPressed); - let mut content = Scrollable::new(&mut self.scroll) .width(Length::Fill) .align_items(Align::Center) .spacing(10) - .push(Space::with_height(Length::Units(800))) + .push(Space::with_height(Length::Units(600))) .push(Text::new("Which is your favorite language?")) .push(pick_list); - content = content - .push(button) - .push(Space::with_height(Length::Units(800))); + content = content.push(Space::with_height(Length::Units(600))); Container::new(content) .width(Length::Fill) -- cgit From 73d1353976e508d918206e2f61b6f45f551e0ff8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 16 Jul 2020 04:30:06 +0200 Subject: Update `README` of examples --- examples/README.md | 1 + examples/pick_list/README.md | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/README.md b/examples/README.md index 8e1b781f..34a916a1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -100,6 +100,7 @@ A bunch of simpler examples exist: - [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). - [`integration`](integration), a demonstration of how to integrate Iced in an existing graphical application. - [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized. +- [`pick_list`](pick_list), a dropdown list of selectable options. - [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI]. - [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider. - [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms. diff --git a/examples/pick_list/README.md b/examples/pick_list/README.md index 4d9fc5b9..6dc80bf4 100644 --- a/examples/pick_list/README.md +++ b/examples/pick_list/README.md @@ -1,18 +1,18 @@ -## Counter +## Pick-list -The classic counter example explained in the [`README`](../../README.md). +A dropdown list of selectable options. + +It displays and positions an overlay based on the window position of the widget. The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: ``` -cargo run --package counter +cargo run --package pick_list ``` [`main`]: src/main.rs -- cgit From 31c30fedd5e5ad74cc1f66162d7e5c0e5e3cf420 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 16 Jul 2020 04:40:36 +0200 Subject: Remove unnecessary `Rc` in both `Element::map` --- native/src/element.rs | 7 +++---- native/src/overlay/element.rs | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/native/src/element.rs b/native/src/element.rs index a1320f18..514a135b 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -2,7 +2,6 @@ use crate::{ layout, overlay, Clipboard, Color, Event, Hasher, Layout, Length, Point, Widget, }; -use std::rc::Rc; /// A generic [`Widget`]. /// @@ -286,7 +285,7 @@ where struct Map<'a, A, B, Renderer> { widget: Box + 'a>, - mapper: Rc B>, + mapper: Box B>, } impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { @@ -299,7 +298,7 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { { Map { widget, - mapper: Rc::new(mapper), + mapper: Box::new(mapper), } } } @@ -370,7 +369,7 @@ where &mut self, layout: Layout<'_>, ) -> Option> { - let mapper = self.mapper.clone(); + let mapper = &self.mapper; self.widget .overlay(layout) diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 3d532126..e1fd9b88 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -1,7 +1,6 @@ pub use crate::Overlay; use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size, Vector}; -use std::rc::Rc; /// A generic [`Overlay`]. /// @@ -38,7 +37,7 @@ where /// Applies a transformation to the produced message of the [`Element`]. /// /// [`Element`]: struct.Element.html - pub fn map(self, f: Rc B>) -> Element<'a, B, Renderer> + pub fn map(self, f: &'a dyn Fn(Message) -> B) -> Element<'a, B, Renderer> where Message: 'a, Renderer: 'a, @@ -104,13 +103,13 @@ where struct Map<'a, A, B, Renderer> { content: Box + 'a>, - mapper: Rc B>, + mapper: &'a dyn Fn(A) -> B, } impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { pub fn new( content: Box + 'a>, - mapper: Rc B + 'static>, + mapper: &'a dyn Fn(A) -> B, ) -> Map<'a, A, B, Renderer> { Map { content, mapper } } -- cgit