use super::renderable_window::{RenderState, RenderableWindow}; use crate::errors::{RenderingError, Result}; use crate::wayland::surfaces::popup_manager::OnCloseCallback; use core::ops::Deref; use layer_shika_domain::value_objects::handle::PopupHandle; use log::info; use slint::{ PhysicalSize, Window, WindowSize, platform::{Renderer, WindowAdapter, WindowEvent, femtovg_renderer::FemtoVGRenderer}, }; use slint_interpreter::ComponentInstance; use std::cell::{Cell, OnceCell, RefCell}; use std::rc::{Rc, Weak}; /// Represents the rendering lifecycle state of a popup window #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum PopupRenderState { /// Awaiting Wayland configure event before rendering can begin Unconfigured, /// Wayland is recalculating geometry; rendering is paused Repositioning, /// Ready to render, no pending changes ReadyClean, /// Ready to render, frame is dirty and needs redraw ReadyDirty, /// Needs an additional layout pass after the next render NeedsRelayout, } pub struct PopupWindow { window: Window, renderer: FemtoVGRenderer, render_state: Cell, size: Cell, scale_factor: Cell, popup_handle: Cell>, on_close: OnceCell, popup_render_state: Cell, component_instance: RefCell>, } impl PopupWindow { #[must_use] pub fn new(renderer: FemtoVGRenderer) -> Rc { Rc::new_cyclic(|weak_self| { let window = Window::new(Weak::clone(weak_self) as Weak); Self { window, renderer, render_state: Cell::new(RenderState::Clean), size: Cell::new(PhysicalSize::default()), scale_factor: Cell::new(1.), popup_handle: Cell::new(None), on_close: OnceCell::new(), popup_render_state: Cell::new(PopupRenderState::Unconfigured), component_instance: RefCell::new(None), } }) } #[must_use] pub fn new_with_callback(renderer: FemtoVGRenderer, on_close: OnCloseCallback) -> Rc { let window = Self::new(renderer); window.on_close.set(on_close).ok(); window } pub fn set_popup_id(&self, handle: PopupHandle) { self.popup_handle.set(Some(handle)); } pub(crate) fn cleanup_resources(&self) { info!("Cleaning up popup window resources to break reference cycles"); if let Err(e) = self.window.hide() { info!("Failed to hide popup window: {e}"); } if let Some(component) = self.component_instance.borrow_mut().take() { info!("Dropping ComponentInstance to break reference cycle"); drop(component); } info!("Popup window resource cleanup complete"); } pub fn close_popup(&self) { info!("Closing popup window - cleaning up resources"); self.cleanup_resources(); if let Some(handle) = self.popup_handle.get() { info!("Destroying popup with handle {:?}", handle); if let Some(on_close) = self.on_close.get() { on_close(handle); } } self.popup_handle.set(None); info!("Popup window cleanup complete"); } pub fn popup_key(&self) -> Option { self.popup_handle.get().map(PopupHandle::key) } pub fn mark_configured(&self) { info!("Popup window marked as configured"); if matches!( self.popup_render_state.get(), PopupRenderState::Unconfigured ) { info!("Transitioning from Unconfigured to ReadyDirty state"); self.popup_render_state.set(PopupRenderState::ReadyDirty); } else { info!( "Preserving current render state to avoid overwriting: {:?}", self.popup_render_state.get() ); } } pub fn is_configured(&self) -> bool { !matches!( self.popup_render_state.get(), PopupRenderState::Unconfigured ) } pub fn set_component_instance(&self, instance: ComponentInstance) { info!("Setting component instance for popup window"); let mut comp = self.component_instance.borrow_mut(); if comp.is_some() { info!("Component instance already set for popup window - replacing"); } *comp = Some(instance); self.window() .dispatch_event(WindowEvent::WindowActiveChanged(true)); } pub fn request_resize(&self, width: f32, height: f32) { info!("Requesting popup resize to {}x{}", width, height); self.set_size(WindowSize::Logical(slint::LogicalSize::new(width, height))); RenderableWindow::request_redraw(self); } pub fn begin_repositioning(&self) { self.popup_render_state.set(PopupRenderState::Repositioning); } pub fn end_repositioning(&self) { self.popup_render_state.set(PopupRenderState::NeedsRelayout); } } impl RenderableWindow for PopupWindow { fn render_frame_if_dirty(&self) -> Result<()> { match self.popup_render_state.get() { PopupRenderState::Unconfigured => { info!("Popup not yet configured, skipping render"); return Ok(()); } PopupRenderState::Repositioning => { info!("Popup repositioning in progress, skipping render"); return Ok(()); } PopupRenderState::ReadyClean => { // Nothing to render return Ok(()); } PopupRenderState::ReadyDirty | PopupRenderState::NeedsRelayout => { // Proceed with rendering } } if matches!( self.render_state.replace(RenderState::Clean), RenderState::Dirty ) { info!( "Rendering popup frame (size: {:?}, scale: {})", self.size.get(), self.scale_factor.get() ); self.renderer .render() .map_err(|e| RenderingError::Operation { message: format!("Error rendering popup frame: {e}"), })?; info!("Popup frame rendered successfully"); if matches!( self.popup_render_state.get(), PopupRenderState::NeedsRelayout ) { info!("Popup needs relayout, requesting additional render"); self.popup_render_state.set(PopupRenderState::ReadyDirty); RenderableWindow::request_redraw(self); } else { self.popup_render_state.set(PopupRenderState::ReadyClean); } } Ok(()) } fn set_scale_factor(&self, scale_factor: f32) { info!("Setting popup scale factor to {scale_factor}"); self.scale_factor.set(scale_factor); self.window() .dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor }); } fn scale_factor(&self) -> f32 { self.scale_factor.get() } fn render_state(&self) -> &Cell { &self.render_state } fn size_cell(&self) -> &Cell { &self.size } } impl WindowAdapter for PopupWindow { fn window(&self) -> &Window { &self.window } fn renderer(&self) -> &dyn Renderer { &self.renderer } fn size(&self) -> PhysicalSize { self.size_impl() } fn set_size(&self, size: WindowSize) { self.set_size_impl(size); } fn request_redraw(&self) { if matches!(self.popup_render_state.get(), PopupRenderState::ReadyClean) { self.popup_render_state.set(PopupRenderState::ReadyDirty); } RenderableWindow::request_redraw(self); } } impl Deref for PopupWindow { type Target = Window; fn deref(&self) -> &Self::Target { &self.window } } impl Drop for PopupWindow { fn drop(&mut self) { info!("PopupWindow being dropped - cleaning up resources"); if let Some(component) = self.component_instance.borrow_mut().take() { info!("Dropping any remaining ComponentInstance in PopupWindow::drop"); drop(component); } info!("PopupWindow drop complete"); } }