use iced::widget::{ self, button, column, container, horizontal_space, row, text, text_input, }; use iced::{ executor, keyboard, subscription, theme, Alignment, Application, Command, Element, Event, Length, Settings, Subscription, }; use self::modal::Modal; pub fn main() -> iced::Result { App::run(Settings::default()) } #[derive(Default)] struct App { show_modal: bool, email: String, password: String, } #[derive(Debug, Clone)] enum Message { ShowModal, HideModal, Email(String), Password(String), Submit, Event(Event), } impl Application for App { type Executor = executor::Default; type Message = Message; type Theme = iced::Theme; type Flags = (); fn new(_flags: ()) -> (Self, Command) { (App::default(), Command::none()) } fn title(&self) -> String { String::from("Modal - Iced") } fn subscription(&self) -> Subscription { subscription::events().map(Message::Event) } fn update(&mut self, message: Message) -> Command { match message { Message::ShowModal => { self.show_modal = true; widget::focus_next() } Message::HideModal => { self.hide_modal(); Command::none() } Message::Email(email) => { self.email = email; Command::none() } Message::Password(password) => { self.password = password; Command::none() } Message::Submit => { if !self.email.is_empty() && !self.password.is_empty() { self.hide_modal(); } Command::none() } Message::Event(event) => match event { Event::Keyboard(keyboard::Event::KeyPressed { key_code: keyboard::KeyCode::Tab, modifiers, }) => { if modifiers.shift() { widget::focus_previous() } else { widget::focus_next() } } Event::Keyboard(keyboard::Event::KeyPressed { key_code: keyboard::KeyCode::Escape, .. }) => { self.hide_modal(); Command::none() } _ => Command::none(), }, } } fn view(&self) -> Element { let content = container( column![ row![ text("Top Left"), horizontal_space(Length::Fill), text("Top Right") ] .align_items(Alignment::Start) .height(Length::Fill), container( button(text("Show Modal")).on_press(Message::ShowModal) ) .center_x() .center_y() .width(Length::Fill) .height(Length::Fill), row![ text("Bottom Left"), horizontal_space(Length::Fill), text("Bottom Right") ] .align_items(Alignment::End) .height(Length::Fill), ] .height(Length::Fill), ) .padding(10) .width(Length::Fill) .height(Length::Fill); if self.show_modal { let modal = container( column![ text("Sign Up").size(24), column![ column![ text("Email").size(12), text_input( "abc@123.com", &self.email, Message::Email ) .on_submit(Message::Submit) .padding(5), ] .spacing(5), column![ text("Password").size(12), text_input("", &self.password, Message::Password) .on_submit(Message::Submit) .password() .padding(5), ] .spacing(5), button(text("Submit")).on_press(Message::HideModal), ] .spacing(10) ] .spacing(20), ) .width(300) .padding(10) .style(theme::Container::Box); Modal::new(content, modal) .on_blur(Message::HideModal) .into() } else { content.into() } } } impl App { fn hide_modal(&mut self) { self.show_modal = false; self.email.clear(); self.password.clear(); } } mod 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<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.base.as_widget().operate( &mut state.children[0], layout, renderer, 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<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.content.as_widget().operate( self.tree, layout.children().next().unwrap(), renderer, 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) } } }