layer_shika_adapters/rendering/femtovg/
popup_window.rs

1use super::renderable_window::{RenderState, RenderableWindow};
2use crate::errors::{RenderingError, Result};
3use crate::wayland::surfaces::popup_manager::OnCloseCallback;
4use core::ops::Deref;
5use layer_shika_domain::dimensions::LogicalSize;
6use layer_shika_domain::value_objects::handle::PopupHandle;
7use log::info;
8use slint::{
9    PhysicalSize, Window, WindowSize,
10    platform::{Renderer, WindowAdapter, WindowEvent, femtovg_renderer::FemtoVGRenderer},
11};
12use slint_interpreter::ComponentInstance;
13use std::cell::{Cell, OnceCell, RefCell};
14use std::rc::{Rc, Weak};
15
16/// Represents the rendering lifecycle state of a popup window
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18enum PopupRenderState {
19    /// Awaiting Wayland configure event before rendering can begin
20    Unconfigured,
21    /// Wayland is recalculating geometry; rendering is paused
22    Repositioning,
23    /// Ready to render, no pending changes
24    ReadyClean,
25    /// Ready to render, frame is dirty and needs redraw
26    ReadyDirty,
27    /// Needs an additional layout pass after the next render
28    NeedsRelayout,
29}
30
31pub struct PopupWindow {
32    window: Window,
33    renderer: FemtoVGRenderer,
34    render_state: Cell<RenderState>,
35    size: Cell<PhysicalSize>,
36    scale_factor: Cell<f32>,
37    popup_handle: Cell<Option<PopupHandle>>,
38    on_close: OnceCell<OnCloseCallback>,
39    popup_render_state: Cell<PopupRenderState>,
40    component_instance: RefCell<Option<ComponentInstance>>,
41    logical_size: Cell<LogicalSize>,
42}
43
44impl PopupWindow {
45    #[must_use]
46    pub fn new(renderer: FemtoVGRenderer) -> Rc<Self> {
47        Rc::new_cyclic(|weak_self| {
48            let window = Window::new(Weak::clone(weak_self) as Weak<dyn WindowAdapter>);
49            Self {
50                window,
51                renderer,
52                render_state: Cell::new(RenderState::Clean),
53                size: Cell::new(PhysicalSize::default()),
54                scale_factor: Cell::new(1.),
55                popup_handle: Cell::new(None),
56                on_close: OnceCell::new(),
57                popup_render_state: Cell::new(PopupRenderState::Unconfigured),
58                component_instance: RefCell::new(None),
59                logical_size: Cell::new(LogicalSize::default()),
60            }
61        })
62    }
63
64    #[must_use]
65    pub fn new_with_callback(renderer: FemtoVGRenderer, on_close: OnCloseCallback) -> Rc<Self> {
66        let window = Self::new(renderer);
67        window.on_close.set(on_close).ok();
68        window
69    }
70
71    pub fn set_popup_id(&self, handle: PopupHandle) {
72        self.popup_handle.set(Some(handle));
73    }
74
75    pub(crate) fn cleanup_resources(&self) {
76        info!("Cleaning up popup window resources to break reference cycles");
77
78        if let Err(e) = self.window.hide() {
79            info!("Failed to hide popup window: {e}");
80        }
81
82        if let Some(component) = self.component_instance.borrow_mut().take() {
83            info!("Dropping ComponentInstance to break reference cycle");
84            drop(component);
85        }
86
87        info!("Popup window resource cleanup complete");
88    }
89
90    pub fn close_popup(&self) {
91        info!("Closing popup window - cleaning up resources");
92
93        self.cleanup_resources();
94
95        if let Some(handle) = self.popup_handle.get() {
96            info!("Destroying popup with handle {:?}", handle);
97            if let Some(on_close) = self.on_close.get() {
98                on_close(handle);
99            }
100        }
101
102        self.popup_handle.set(None);
103
104        info!("Popup window cleanup complete");
105    }
106
107    pub fn popup_key(&self) -> Option<usize> {
108        self.popup_handle.get().map(PopupHandle::key)
109    }
110
111    pub fn mark_configured(&self) {
112        info!("Popup window marked as configured");
113
114        if matches!(
115            self.popup_render_state.get(),
116            PopupRenderState::Unconfigured
117        ) {
118            info!("Transitioning from Unconfigured to ReadyDirty state");
119            self.popup_render_state.set(PopupRenderState::ReadyDirty);
120        } else {
121            info!(
122                "Preserving current render state to avoid overwriting: {:?}",
123                self.popup_render_state.get()
124            );
125        }
126    }
127
128    pub fn is_configured(&self) -> bool {
129        !matches!(
130            self.popup_render_state.get(),
131            PopupRenderState::Unconfigured
132        )
133    }
134
135    pub fn set_component_instance(&self, instance: ComponentInstance) {
136        info!("Setting component instance for popup window");
137        let mut comp = self.component_instance.borrow_mut();
138        if comp.is_some() {
139            info!("Component instance already set for popup window - replacing");
140        }
141        *comp = Some(instance);
142
143        self.window()
144            .dispatch_event(WindowEvent::WindowActiveChanged(true));
145    }
146
147    pub fn request_resize(&self, width: f32, height: f32) -> bool {
148        let new_size = LogicalSize::from_raw(width, height);
149        let current_size = self.logical_size.get();
150
151        if current_size == new_size {
152            info!(
153                "Popup resize skipped - size unchanged: {}x{}",
154                width, height
155            );
156            return false;
157        }
158
159        info!(
160            "Requesting popup resize from {}x{} to {}x{}",
161            current_size.width(),
162            current_size.height(),
163            width,
164            height
165        );
166        self.logical_size.set(new_size);
167        self.set_size(WindowSize::Logical(slint::LogicalSize::new(width, height)));
168        RenderableWindow::request_redraw(self);
169        true
170    }
171
172    pub fn begin_repositioning(&self) {
173        self.popup_render_state.set(PopupRenderState::Repositioning);
174    }
175
176    pub fn end_repositioning(&self) {
177        self.popup_render_state.set(PopupRenderState::NeedsRelayout);
178    }
179}
180
181impl RenderableWindow for PopupWindow {
182    fn render_frame_if_dirty(&self) -> Result<()> {
183        match self.popup_render_state.get() {
184            PopupRenderState::Unconfigured => {
185                info!("Popup not yet configured, skipping render");
186                return Ok(());
187            }
188            PopupRenderState::Repositioning => {
189                info!("Popup repositioning in progress, skipping render");
190                return Ok(());
191            }
192            PopupRenderState::ReadyClean => {
193                // Nothing to render
194                return Ok(());
195            }
196            PopupRenderState::ReadyDirty | PopupRenderState::NeedsRelayout => {
197                // Proceed with rendering
198            }
199        }
200
201        if matches!(
202            self.render_state.replace(RenderState::Clean),
203            RenderState::Dirty
204        ) {
205            self.renderer
206                .render()
207                .map_err(|e| RenderingError::Operation {
208                    message: format!("Error rendering popup frame: {e}"),
209                })?;
210
211            if matches!(
212                self.popup_render_state.get(),
213                PopupRenderState::NeedsRelayout
214            ) {
215                info!("Popup needs relayout, requesting additional render");
216                self.popup_render_state.set(PopupRenderState::ReadyDirty);
217                RenderableWindow::request_redraw(self);
218            } else {
219                self.popup_render_state.set(PopupRenderState::ReadyClean);
220            }
221        }
222        Ok(())
223    }
224
225    fn set_scale_factor(&self, scale_factor: f32) {
226        info!("Setting popup scale factor to {scale_factor}");
227        self.scale_factor.set(scale_factor);
228        self.window()
229            .dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
230    }
231
232    fn scale_factor(&self) -> f32 {
233        self.scale_factor.get()
234    }
235
236    fn render_state(&self) -> &Cell<RenderState> {
237        &self.render_state
238    }
239
240    fn size_cell(&self) -> &Cell<PhysicalSize> {
241        &self.size
242    }
243}
244
245impl WindowAdapter for PopupWindow {
246    fn window(&self) -> &Window {
247        &self.window
248    }
249
250    fn renderer(&self) -> &dyn Renderer {
251        &self.renderer
252    }
253
254    fn size(&self) -> PhysicalSize {
255        self.size_impl()
256    }
257
258    fn set_size(&self, size: WindowSize) {
259        self.set_size_impl(size);
260    }
261
262    fn request_redraw(&self) {
263        if matches!(self.popup_render_state.get(), PopupRenderState::ReadyClean) {
264            self.popup_render_state.set(PopupRenderState::ReadyDirty);
265        }
266        RenderableWindow::request_redraw(self);
267    }
268}
269
270impl Deref for PopupWindow {
271    type Target = Window;
272    fn deref(&self) -> &Self::Target {
273        &self.window
274    }
275}
276
277impl Drop for PopupWindow {
278    fn drop(&mut self) {
279        info!("PopupWindow being dropped - cleaning up resources");
280
281        if let Some(component) = self.component_instance.borrow_mut().take() {
282            info!("Dropping any remaining ComponentInstance in PopupWindow::drop");
283            drop(component);
284        }
285
286        info!("PopupWindow drop complete");
287    }
288}