Simplify modal example 🎉

This commit is contained in:
Héctor Ramón Jiménez 2024-04-25 06:05:40 +02:00
parent 4cd45643d7
commit 4fc342c979
No known key found for this signature in database
GPG key ID: 7CC46565708259A7

View file

@ -2,12 +2,11 @@ use iced::event::{self, Event};
use iced::keyboard; use iced::keyboard;
use iced::keyboard::key; use iced::keyboard::key;
use iced::widget::{ use iced::widget::{
self, button, column, container, horizontal_space, pick_list, row, text, self, button, column, container, horizontal_space, mouse_area, opaque,
text_input, pick_list, row, stack, text, text_input,
}; };
use iced::{Alignment, Command, Element, Length, Subscription}; use iced::{Alignment, Color, Command, Element, Length, Subscription};
use modal::Modal;
use std::fmt; use std::fmt;
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
@ -121,7 +120,7 @@ impl App {
.height(Length::Fill); .height(Length::Fill);
if self.show_modal { if self.show_modal {
let modal = container( let signup = container(
column![ column![
text("Sign Up").size(24), text("Sign Up").size(24),
column![ column![
@ -162,9 +161,7 @@ impl App {
.padding(10) .padding(10)
.style(container::rounded_box); .style(container::rounded_box);
Modal::new(content, modal) modal(content, signup, Message::HideModal)
.on_blur(Message::HideModal)
.into()
} else { } else {
content.into() content.into()
} }
@ -203,326 +200,36 @@ impl fmt::Display for Plan {
} }
} }
mod modal { fn modal<'a, Message>(
use iced::advanced::layout::{self, Layout}; base: impl Into<Element<'a, Message>>,
use iced::advanced::overlay; content: impl Into<Element<'a, Message>>,
use iced::advanced::renderer; on_blur: Message,
use iced::advanced::widget::{self, Widget}; ) -> Element<'a, Message>
use iced::advanced::{self, Clipboard, Shell}; where
use iced::alignment::Alignment; Message: Clone + 'a,
use iced::event; {
use iced::mouse; stack![
use iced::{Color, Element, Event, Length, Point, Rectangle, Size, Vector}; base.into(),
mouse_area(
/// A widget that centers a modal element over some base element container(opaque(content))
pub struct Modal<'a, Message, Theme, Renderer> {
base: Element<'a, Message, Theme, Renderer>,
modal: Element<'a, Message, Theme, Renderer>,
on_blur: Option<Message>,
}
impl<'a, Message, Theme, Renderer> Modal<'a, Message, Theme, Renderer> {
/// Returns a new [`Modal`]
pub fn new(
base: impl Into<Element<'a, Message, Theme, Renderer>>,
modal: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> 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, Theme, Renderer> Widget<Message, Theme, Renderer>
for Modal<'a, Message, Theme, Renderer>
where
Renderer: advanced::Renderer,
Message: Clone,
{
fn children(&self) -> Vec<widget::Tree> {
vec![
widget::Tree::new(&self.base),
widget::Tree::new(&self.modal),
]
}
fn diff(&self, tree: &mut widget::Tree) {
tree.diff_children(&[&self.base, &self.modal]);
}
fn size(&self) -> Size<Length> {
self.base.as_widget().size()
}
fn layout(
&self,
tree: &mut widget::Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.base.as_widget().layout(
&mut tree.children[0],
renderer,
limits,
)
}
fn on_event(
&mut self,
state: &mut widget::Tree,
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
self.base.as_widget_mut().on_event(
&mut state.children[0],
event,
layout,
cursor,
renderer,
clipboard,
shell,
viewport,
)
}
fn draw(
&self,
state: &widget::Tree,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
&state.children[0],
renderer,
theme,
style,
layout,
cursor,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
state: &'b mut widget::Tree,
layout: Layout<'_>,
_renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
Some(overlay::Element::new(Box::new(Overlay {
position: layout.position() + translation,
content: &mut self.modal,
tree: &mut state.children[1],
size: layout.bounds().size(),
on_blur: self.on_blur.clone(),
})))
}
fn mouse_interaction(
&self,
state: &widget::Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor,
viewport,
renderer,
)
}
fn operate(
&self,
state: &mut widget::Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.base.as_widget().operate(
&mut state.children[0],
layout,
renderer,
operation,
);
}
}
struct Overlay<'a, 'b, Message, Theme, Renderer> {
position: Point,
content: &'b mut Element<'a, Message, Theme, Renderer>,
tree: &'b mut widget::Tree,
size: Size,
on_blur: Option<Message>,
}
impl<'a, 'b, Message, Theme, Renderer>
overlay::Overlay<Message, Theme, Renderer>
for Overlay<'a, 'b, Message, Theme, Renderer>
where
Renderer: advanced::Renderer,
Message: Clone,
{
fn layout(
&mut self,
renderer: &Renderer,
_bounds: Size,
) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, self.size)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill); .height(Length::Fill)
.center_x()
let child = self .center_y()
.content .style(|_theme| {
.as_widget() container::Style {
.layout(self.tree, renderer, &limits) background: Some(
.align(Alignment::Center, Alignment::Center, limits.max()); Color {
a: 0.8,
layout::Node::with_children(self.size, vec![child]) ..Color::BLACK
.move_to(self.position) }
} .into(),
),
fn on_event( ..container::Style::default()
&mut self,
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
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 !cursor.is_over(content_bounds) {
shell.publish(message.clone());
return event::Status::Captured;
} }
} })
} )
.on_press(on_blur)
self.content.as_widget_mut().on_event( ]
self.tree, .into()
event,
layout.children().next().unwrap(),
cursor,
renderer,
clipboard,
shell,
&layout.bounds(),
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
..renderer::Quad::default()
},
Color {
a: 0.80,
..Color::BLACK
},
);
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
layout.children().next().unwrap(),
cursor,
&layout.bounds(),
);
}
fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.content.as_widget().operate(
self.tree,
layout.children().next().unwrap(),
renderer,
operation,
);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
cursor,
viewport,
renderer,
)
}
fn overlay<'c>(
&'c mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
self.tree,
layout.children().next().unwrap(),
renderer,
Vector::ZERO,
)
}
}
impl<'a, Message, Theme, Renderer> From<Modal<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Theme: 'a,
Message: 'a + Clone,
Renderer: 'a + advanced::Renderer,
{
fn from(modal: Modal<'a, Message, Theme, Renderer>) -> Self {
Element::new(modal)
}
}
} }