use std::{cell::RefCell, rc::Rc}; use glam::Vec2; use taffy::prelude::{length, percent}; use crate::{ animation::{Animation, AnimationEasing}, assets::AssetPath, components::button::ComponentButton, drawing, event::{EventListenerKind, StyleSetRequest}, globals::WguiGlobals, i18n::Translation, layout::{Layout, LayoutTask, LayoutTasks, WidgetPair}, parser::{self, Fetchable, ParserState}, widget::{ EventResult, div::WidgetDiv, label::WidgetLabel, rectangle::{WidgetRectangle, WidgetRectangleParams}, }, }; struct OpenedWindow { layout_tasks: LayoutTasks, widget: WidgetPair, input_grabber: Option, content: WidgetPair, #[allow(dead_code)] state: Option, } impl Drop for OpenedWindow { fn drop(&mut self) { self.layout_tasks.push(LayoutTask::RemoveWidget(self.widget.id)); if let Some(grabber) = &self.input_grabber { self.layout_tasks.push(LayoutTask::RemoveWidget(grabber.id)); } } } struct State { opened_window: Option, } #[derive(Clone)] pub struct WguiWindow(Rc>); pub struct OnContentData { pub widget: WidgetPair, } #[derive(Default)] pub enum WguiWindowPlacement { #[default] TopLeft, BottomLeft, TopRight, BottomRight, } pub struct WguiWindowParamsExtra { pub fixed_width: Option, pub fixed_height: Option, pub placement: WguiWindowPlacement, pub with_decorations: bool, pub no_decoration_padding: f32, pub close_if_clicked_outside: bool, pub title: Option, } impl Default for WguiWindowParamsExtra { fn default() -> Self { Self { fixed_width: None, fixed_height: None, title: None, placement: WguiWindowPlacement::TopLeft, no_decoration_padding: 0.0, close_if_clicked_outside: false, with_decorations: true, } } } pub struct WguiWindowParams<'a> { pub position: Vec2, pub globals: &'a WguiGlobals, pub layout: &'a mut Layout, pub extra: WguiWindowParamsExtra, } impl Default for WguiWindow { fn default() -> Self { Self(Rc::new(RefCell::new(State { opened_window: None }))) } } const WINDOW_DECORATION_PADDING: f32 = 2.0; const WINDOW_DECORATION_HEADER_HEIGHT: f32 = 32.0; impl WguiWindow { pub fn close(&self) { self.0.borrow_mut().opened_window = None; } #[allow(clippy::too_many_lines)] pub fn open(&mut self, params: &mut WguiWindowParams) -> anyhow::Result<()> { // close previous one if it's already open self.close(); let header_height = if params.extra.with_decorations { WINDOW_DECORATION_HEADER_HEIGHT } else { 0.0 }; let window_padding = if params.extra.with_decorations { WINDOW_DECORATION_PADDING } else { params.extra.no_decoration_padding }; let (padding, justify_content, align_items) = match params.extra.placement { WguiWindowPlacement::TopLeft => ( taffy::Rect { left: length(params.position.x - window_padding), top: length(params.position.y - header_height - window_padding), bottom: length(0.0), right: length(0.0), }, taffy::JustifyContent::Start, // x start taffy::AlignItems::Start, // y start ), WguiWindowPlacement::BottomLeft => ( taffy::Rect { left: length(params.position.x - window_padding), top: length(0.0), bottom: length(params.position.y - window_padding), right: length(0.0), }, taffy::JustifyContent::Start, // x start taffy::AlignItems::End, // y end ), WguiWindowPlacement::TopRight => ( taffy::Rect { left: length(0.0), top: length(params.position.y - header_height - window_padding), bottom: length(0.0), right: length(params.position.x - window_padding), }, taffy::JustifyContent::End, // x end taffy::AlignItems::Start, // y start ), WguiWindowPlacement::BottomRight => ( taffy::Rect { left: length(0.0), top: length(0.0), bottom: length(params.position.y - window_padding), right: length(params.position.x - window_padding), }, taffy::JustifyContent::End, // x end taffy::AlignItems::End, // y end ), }; let input_grabber = if params.extra.close_if_clicked_outside { let mut rect = WidgetRectangle::create(Default::default()); rect.flags.consume_mouse_events = true; let this = self.clone(); rect.event_listeners.register( EventListenerKind::MousePress, Box::new(move |_, _, (), ()| { this.close(); Ok(EventResult::Consumed) }), ); let (widget, _) = params.layout.add_topmost_child( rect, taffy::Style { position: taffy::Position::Absolute, size: taffy::Size { width: percent(1.0), height: percent(1.0), }, ..Default::default() }, )?; // Fade animation params.layout.animations.add(Animation::new( widget.id, 20, AnimationEasing::OutQuad, Box::new(|common, data| { let rect = data.obj.get_as_mut::().unwrap() /* should always succeed */; rect.params.color = drawing::Color::new(0.0, 0.0, 0.0, data.pos * 0.3); common.alterables.mark_redraw(); }), )); Some(widget) } else { None }; let (widget, _) = params.layout.add_topmost_child( WidgetDiv::create(), taffy::Style { position: taffy::Position::Absolute, align_items: Some(align_items), justify_content: Some(justify_content), padding, size: taffy::Size { width: percent(1.0), height: percent(1.0), }, ..Default::default() }, )?; let content_id = if params.extra.with_decorations { let xml_path: AssetPath = AssetPath::WguiInternal("wgui/window_frame.xml"); let state = parser::parse_from_assets( &parser::ParseDocumentParams { globals: params.globals.clone(), path: xml_path, extra: Default::default(), }, params.layout, widget.id, )?; let but_close = state.fetch_component_as::("but_close").unwrap(); but_close.on_click({ let this = self.clone(); Box::new(move |_common, _e| { this.close(); Ok(()) }) }); if let Some(title) = ¶ms.extra.title { let mut text_title = state.fetch_widget_as::(¶ms.layout.state, "text_window_title")?; text_title.set_text_simple(&mut params.globals.get(), title.clone()); } let content = state.fetch_widget(¶ms.layout.state, "content")?; self.0.borrow_mut().opened_window = Some(OpenedWindow { widget, state: Some(state), layout_tasks: params.layout.tasks.clone(), content: content.clone(), input_grabber, }); content.id } else { // without decorations let (content, _) = params .layout .add_child(widget.id, WidgetDiv::create(), Default::default())?; self.0.borrow_mut().opened_window = Some(OpenedWindow { widget, state: None, layout_tasks: params.layout.tasks.clone(), content: content.clone(), input_grabber, }); content.id }; let mut c = params.layout.start_common(); if let Some(width) = params.extra.fixed_width { c.common() .alterables .set_style(content_id, StyleSetRequest::Width(length(width))); } if let Some(height) = params.extra.fixed_height { c.common() .alterables .set_style(content_id, StyleSetRequest::Height(length(height))); } c.finish()?; Ok(()) } pub fn get_content(&self) -> WidgetPair { let state = self.0.borrow_mut(); state.opened_window.as_ref().unwrap().content.clone() } }