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` --- native/src/overlay/content.rs | 34 +++++ native/src/overlay/menu.rs | 323 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 native/src/overlay/content.rs create mode 100644 native/src/overlay/menu.rs (limited to 'native/src/overlay') 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) + } +} -- 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` --- native/src/overlay/menu.rs | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) (limited to 'native/src/overlay') 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) -- 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 --- native/src/overlay/menu.rs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) (limited to 'native/src/overlay') 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( -- 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(-) (limited to 'native/src/overlay') 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 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) (limited to 'native/src/overlay') 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, -- 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/menu.rs | 124 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 28 deletions(-) (limited to 'native/src/overlay') 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> { -- 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` --- native/src/overlay/menu.rs | 51 +++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 30 deletions(-) (limited to 'native/src/overlay') 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; } -- 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/overlay/content.rs | 34 ---------- native/src/overlay/element.rs | 145 ++++++++++++++++++++++++++++++++++++++++++ native/src/overlay/menu.rs | 6 +- 3 files changed, 148 insertions(+), 37 deletions(-) delete mode 100644 native/src/overlay/content.rs create mode 100644 native/src/overlay/element.rs (limited to 'native/src/overlay') 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, -- 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 --- native/src/overlay/menu.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native/src/overlay') 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, -- 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 --- native/src/overlay/element.rs | 56 +++++++++++++++++++++++++++++----------- native/src/overlay/menu.rs | 60 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 16 deletions(-) (limited to 'native/src/overlay') 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, -- 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/overlay/element.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'native/src/overlay') 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