From 7ffde7381b166acabd121e1915365cc8b517d566 Mon Sep 17 00:00:00 2001 From: edouard Date: Thu, 29 Dec 2022 17:35:39 +0100 Subject: [PATCH] ui: add modal component --- gui/src/ui/component/mod.rs | 1 + gui/src/ui/component/modal.rs | 273 ++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 gui/src/ui/component/modal.rs diff --git a/gui/src/ui/component/mod.rs b/gui/src/ui/component/mod.rs index cc41b055..3f97ac8d 100644 --- a/gui/src/ui/component/mod.rs +++ b/gui/src/ui/component/mod.rs @@ -4,6 +4,7 @@ pub mod card; pub mod collapse; pub mod container; pub mod form; +pub mod modal; pub mod notification; pub mod text; diff --git a/gui/src/ui/component/modal.rs b/gui/src/ui/component/modal.rs new file mode 100644 index 00000000..13716482 --- /dev/null +++ b/gui/src/ui/component/modal.rs @@ -0,0 +1,273 @@ +/// modal widget from https://github.com/iced-rs/iced/blob/master/examples/modal/ +use iced_native::alignment::Alignment; +use iced_native::widget::{self, Tree}; +use iced_native::{ + event, layout, mouse, overlay, renderer, Clipboard, Color, Element, Event, Layout, Length, + Point, Rectangle, Shell, Size, Widget, +}; + +/// A widget that centers a modal element over some base element +pub struct Modal<'a, Message, Renderer> { + base: Element<'a, Message, Renderer>, + modal: Element<'a, Message, Renderer>, + on_blur: Option, +} + +impl<'a, Message, Renderer> Modal<'a, Message, Renderer> { + /// Returns a new [`Modal`] + pub fn new( + base: impl Into>, + modal: impl Into>, + ) -> Self { + Self { + base: base.into(), + modal: modal.into(), + on_blur: None, + } + } + + /// Sets the message that will be produces when the background + /// of the [`Modal`] is pressed + pub fn on_blur(self, on_blur: Message) -> Self { + Self { + on_blur: Some(on_blur), + ..self + } + } +} + +impl<'a, Message, Renderer> Widget for Modal<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, + Message: Clone, +{ + fn children(&self) -> Vec { + vec![Tree::new(&self.base), Tree::new(&self.modal)] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(&[&self.base, &self.modal]); + } + + fn width(&self) -> Length { + self.base.as_widget().width() + } + + fn height(&self) -> Length { + self.base.as_widget().height() + } + + fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + self.base.as_widget().layout(renderer, limits) + } + + fn on_event( + &mut self, + state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.base.as_widget_mut().on_event( + &mut state.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + state: &Tree, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.base.as_widget().draw( + &state.children[0], + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + state: &'b mut Tree, + layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option> { + Some(overlay::Element::new( + layout.position(), + Box::new(Overlay { + content: &mut self.modal, + tree: &mut state.children[1], + size: layout.bounds().size(), + on_blur: self.on_blur.clone(), + }), + )) + } + + fn mouse_interaction( + &self, + state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.base.as_widget().mouse_interaction( + &state.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn operate( + &self, + state: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation, + ) { + self.base + .as_widget() + .operate(&mut state.children[0], layout, operation); + } +} + +struct Overlay<'a, 'b, Message, Renderer> { + content: &'b mut Element<'a, Message, Renderer>, + tree: &'b mut Tree, + size: Size, + on_blur: Option, +} + +impl<'a, 'b, Message, Renderer> overlay::Overlay + for Overlay<'a, 'b, Message, Renderer> +where + Renderer: iced_native::Renderer, + Message: Clone, +{ + fn layout(&self, renderer: &Renderer, _bounds: Size, position: Point) -> layout::Node { + let limits = layout::Limits::new(Size::ZERO, self.size) + .width(Length::Fill) + .height(Length::Fill); + + let mut child = self.content.as_widget().layout(renderer, &limits); + child.align(Alignment::Center, Alignment::Center, limits.max()); + + let mut node = layout::Node::with_children(self.size, vec![child]); + node.move_to(position); + + node + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let content_bounds = layout.children().next().unwrap().bounds(); + + if let Some(message) = self.on_blur.as_ref() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = &event { + if !content_bounds.contains(cursor_position) { + shell.publish(message.clone()); + return event::Status::Captured; + } + } + } + + self.content.as_widget_mut().on_event( + self.tree, + event, + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + renderer.fill_quad( + renderer::Quad { + bounds: layout.bounds(), + border_radius: renderer::BorderRadius::from(0.0), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Color { + a: 0.80, + ..Color::BLACK + }, + ); + + self.content.as_widget().draw( + self.tree, + renderer, + theme, + style, + layout.children().next().unwrap(), + cursor_position, + &layout.bounds(), + ); + } + + fn operate(&mut self, layout: Layout<'_>, operation: &mut dyn widget::Operation) { + self.content + .as_widget() + .operate(self.tree, layout.children().next().unwrap(), operation); + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + self.tree, + layout.children().next().unwrap(), + cursor_position, + viewport, + renderer, + ) + } +} + +impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> +where + Renderer: 'a + iced_native::Renderer, + Message: 'a + Clone, +{ + fn from(modal: Modal<'a, Message, Renderer>) -> Self { + Element::new(modal) + } +}