use crate::data; use druid::widget::{Controller, Padding}; use druid::{ widget::{Container, Flex, Label, LensWrap, TextBox}, Color, FontDescriptor, FontFamily, FontWeight, RenderContext, Widget, WidgetExt, WidgetPod, }; use druid::{Data, LifeCycle, TextAlignment}; pub struct ModalContainer { child: WidgetPod>>, modal: Option>>>, } impl ModalContainer { pub fn new(child: impl Widget + 'static) -> Self { Self { child: WidgetPod::new(child).boxed(), modal: None, } } } impl Widget for ModalContainer { fn event( &mut self, ctx: &mut druid::EventCtx, event: &druid::Event, data: &mut data::AppData, env: &druid::Env, ) { if let druid::Event::Notification(n) = event { if n.is(crate::commands::CLOSE_MODAL) { self.modal = None; data.modals.rename_block = data::RenameBlock::default(); ctx.children_changed(); return; } } if let druid::Event::Command(c) = event { if let Some(idx) = c.get(crate::commands::RENAME_BLOCK) { data.modals.rename_block = data::RenameBlock { name: data .blocks .get(*idx) .map(|i| i.name.clone()) .unwrap_or_default(), input: data .blocks .get(*idx) .map(|i| i.name.clone()) .unwrap_or_default(), block_index: *idx, }; self.modal = Some(WidgetPod::new(rename_block()).boxed()); ctx.children_changed(); return; } } if let Some(m) = self.modal.as_mut() { m.event(ctx, event, data, env); } else { self.child.event(ctx, event, data, env); } } fn lifecycle( &mut self, ctx: &mut druid::LifeCycleCtx, event: &druid::LifeCycle, data: &data::AppData, env: &druid::Env, ) { if let Some(modal) = self.modal.as_mut() { modal.lifecycle(ctx, event, data, env); } self.child.lifecycle(ctx, event, data, env); } fn update( &mut self, ctx: &mut druid::UpdateCtx, _old_data: &data::AppData, data: &data::AppData, env: &druid::Env, ) { if let Some(modal) = self.modal.as_mut() { modal.update(ctx, data, env); } self.child.update(ctx, data, env); } fn layout( &mut self, ctx: &mut druid::LayoutCtx, bc: &druid::BoxConstraints, data: &data::AppData, env: &druid::Env, ) -> druid::Size { if let Some(modal) = self.modal.as_mut() { modal.layout(ctx, bc, data, env); } self.child.layout(ctx, bc, data, env) } fn paint(&mut self, ctx: &mut druid::PaintCtx, data: &data::AppData, env: &druid::Env) { self.child.paint(ctx, data, env); let full_rect = ctx.size().to_rect(); if let Some(m) = self.modal.as_mut() { ctx.fill(full_rect, &druid::Color::rgba8(0, 0, 0, 100)); m.paint(ctx, data, env) } } } fn rename_block() -> impl Widget { modal_container( "Rename Block", (300.0, 150.0), Flex::column() .with_child( Label::new("Block name") .with_text_color(druid::Color::WHITE) .with_text_alignment(TextAlignment::Start) .with_font( FontDescriptor::new(FontFamily::SYSTEM_UI) .with_weight(FontWeight::BOLD) .with_size(14.0), ) .fix_width(300.0), ) .with_spacer(5.0) .with_child(LensWrap::new( LensWrap::new( TextBox::default() .lens(data::RenameBlock::input) .fix_width(300.0), data::Modals::rename_block, ), data::AppData::modals, )) .with_spacer(20.0) .with_child( Flex::row() .must_fill_main_axis(true) .with_child(modal_action("Cancel").on_click(|ctx, _, _| { ctx.submit_notification(crate::commands::CLOSE_MODAL) })) .with_flex_spacer(1.0) .with_child(modal_action("Rename").on_click( |ctx, data: &mut data::AppData, _| { let input = data.modals.rename_block.input.clone(); if let Some(blk) = data.blocks.get_mut(data.modals.rename_block.block_index) { blk.name = input; data.modals.rename_block = data::RenameBlock::default(); ctx.submit_notification(crate::commands::CLOSE_MODAL) } }, )) .fix_width(300.0), ), ) } fn modal_container>( title: &str, size: S, child: impl Widget + 'static, ) -> impl Widget { let size = size.into(); Flex::column() .with_child(modal_title(title, size.width)) .with_child(Padding::new(15.0, child)) .background(Color::rgb8(20, 20, 20)) .center() .fix_size(size.width, size.height) } fn modal_title(title: &str, width: f64) -> impl Widget { Container::new(Padding::new( 8.0, Flex::row() .with_spacer(10.0) .with_child( Label::new(title) .with_text_color(Color::rgb8(150, 150, 150)) .with_text_alignment(TextAlignment::Start) .with_font( FontDescriptor::new(FontFamily::SYSTEM_UI) .with_weight(FontWeight::BOLD) .with_size(14.0), ) .fix_width(width), ) .with_spacer(10.0), )) .background(Color::rgb8(10, 10, 10)) } fn modal_action(text: &str) -> impl Widget { Container::new(Padding::new(5.0, Label::new(text))) .rounded(4.0) .controller(ModalActionController) } pub struct ModalActionController; impl Controller> for ModalActionController { fn event( &mut self, child: &mut Container, ctx: &mut druid::EventCtx, event: &druid::Event, data: &mut T, env: &druid::Env, ) { ctx.set_cursor(&druid::Cursor::Pointer); child.event(ctx, event, data, env) } fn lifecycle( &mut self, child: &mut Container, ctx: &mut druid::LifeCycleCtx, event: &druid::LifeCycle, data: &T, env: &druid::Env, ) { match event { LifeCycle::HotChanged(true) => { child.set_background(Color::rgb8(100, 100, 100)); ctx.request_paint(); } LifeCycle::HotChanged(false) => { child.set_background(Color::TRANSPARENT); ctx.request_paint(); } _ => {} } child.lifecycle(ctx, event, data, env) } fn update( &mut self, child: &mut Container, ctx: &mut druid::UpdateCtx, old_data: &T, data: &T, env: &druid::Env, ) { child.update(ctx, old_data, data, env) } }