layer_shika_adapters/rendering/femtovg/
popup_window.rs1use 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::value_objects::popup_request::PopupHandle;
6use log::info;
7use slint::{
8 PhysicalSize, Window, WindowSize,
9 platform::{Renderer, WindowAdapter, WindowEvent, femtovg_renderer::FemtoVGRenderer},
10};
11use slint_interpreter::ComponentInstance;
12use std::cell::{Cell, OnceCell, RefCell};
13use std::rc::{Rc, Weak};
14
15pub struct PopupWindow {
16 window: Window,
17 renderer: FemtoVGRenderer,
18 render_state: Cell<RenderState>,
19 size: Cell<PhysicalSize>,
20 scale_factor: Cell<f32>,
21 popup_handle: Cell<Option<PopupHandle>>,
22 on_close: OnceCell<OnCloseCallback>,
23 configured: Cell<bool>,
24 repositioning: Cell<bool>,
25 needs_relayout: Cell<bool>,
26 component_instance: RefCell<Option<ComponentInstance>>,
27}
28
29impl PopupWindow {
30 #[must_use]
31 pub fn new(renderer: FemtoVGRenderer) -> Rc<Self> {
32 Rc::new_cyclic(|weak_self| {
33 let window = Window::new(Weak::clone(weak_self) as Weak<dyn WindowAdapter>);
34 Self {
35 window,
36 renderer,
37 render_state: Cell::new(RenderState::Clean),
38 size: Cell::new(PhysicalSize::default()),
39 scale_factor: Cell::new(1.),
40 popup_handle: Cell::new(None),
41 on_close: OnceCell::new(),
42 configured: Cell::new(false),
43 repositioning: Cell::new(false),
44 needs_relayout: Cell::new(false),
45 component_instance: RefCell::new(None),
46 }
47 })
48 }
49
50 #[must_use]
51 pub fn new_with_callback(renderer: FemtoVGRenderer, on_close: OnCloseCallback) -> Rc<Self> {
52 let window = Self::new(renderer);
53 window.on_close.set(on_close).ok();
54 window
55 }
56
57 pub fn set_popup_id(&self, handle: PopupHandle) {
58 self.popup_handle.set(Some(handle));
59 }
60
61 pub(crate) fn cleanup_resources(&self) {
62 info!("Cleaning up popup window resources to break reference cycles");
63
64 if let Err(e) = self.window.hide() {
65 info!("Failed to hide popup window: {e}");
66 }
67
68 if let Some(component) = self.component_instance.borrow_mut().take() {
69 info!("Dropping ComponentInstance to break reference cycle");
70 drop(component);
71 }
72
73 info!("Popup window resource cleanup complete");
74 }
75
76 pub fn close_popup(&self) {
77 info!("Closing popup window - cleaning up resources");
78
79 self.cleanup_resources();
80
81 if let Some(handle) = self.popup_handle.get() {
82 info!("Destroying popup with handle {:?}", handle);
83 if let Some(on_close) = self.on_close.get() {
84 on_close(handle);
85 }
86 }
87
88 self.popup_handle.set(None);
89
90 info!("Popup window cleanup complete");
91 }
92
93 pub fn popup_key(&self) -> Option<usize> {
94 self.popup_handle.get().map(PopupHandle::key)
95 }
96
97 pub fn mark_configured(&self) {
98 info!("Popup window marked as configured");
99 self.configured.set(true);
100 }
101
102 pub fn is_configured(&self) -> bool {
103 self.configured.get()
104 }
105
106 pub fn set_component_instance(&self, instance: ComponentInstance) {
107 info!("Setting component instance for popup window");
108 let mut comp = self.component_instance.borrow_mut();
109 if comp.is_some() {
110 info!("Component instance already set for popup window - replacing");
111 }
112 *comp = Some(instance);
113 }
114
115 pub fn request_resize(&self, width: f32, height: f32) {
116 info!("Requesting popup resize to {}x{}", width, height);
117 self.set_size(WindowSize::Logical(slint::LogicalSize::new(width, height)));
118 RenderableWindow::request_redraw(self);
119 }
120
121 pub fn begin_repositioning(&self) {
122 self.repositioning.set(true);
123 }
124
125 pub fn end_repositioning(&self) {
126 self.repositioning.set(false);
127 self.needs_relayout.set(true);
128 }
129}
130
131impl RenderableWindow for PopupWindow {
132 fn render_frame_if_dirty(&self) -> Result<()> {
133 if !self.configured.get() {
134 info!("Popup not yet configured, skipping render");
135 return Ok(());
136 }
137
138 if self.repositioning.get() {
139 info!("Popup repositioning in progress, skipping render");
140 return Ok(());
141 }
142
143 if matches!(
144 self.render_state.replace(RenderState::Clean),
145 RenderState::Dirty
146 ) {
147 info!(
148 "Rendering popup frame (size: {:?}, scale: {})",
149 self.size.get(),
150 self.scale_factor.get()
151 );
152 self.renderer
153 .render()
154 .map_err(|e| RenderingError::Operation {
155 message: format!("Error rendering popup frame: {e}"),
156 })?;
157 info!("Popup frame rendered successfully");
158
159 if self.needs_relayout.get() {
160 info!("Popup needs relayout, requesting additional render");
161 self.needs_relayout.set(false);
162 RenderableWindow::request_redraw(self);
163 }
164 }
165 Ok(())
166 }
167
168 fn set_scale_factor(&self, scale_factor: f32) {
169 info!("Setting popup scale factor to {scale_factor}");
170 self.scale_factor.set(scale_factor);
171 self.window()
172 .dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
173 }
174
175 fn scale_factor(&self) -> f32 {
176 self.scale_factor.get()
177 }
178
179 fn render_state(&self) -> &Cell<RenderState> {
180 &self.render_state
181 }
182
183 fn size_cell(&self) -> &Cell<PhysicalSize> {
184 &self.size
185 }
186}
187
188impl WindowAdapter for PopupWindow {
189 fn window(&self) -> &Window {
190 &self.window
191 }
192
193 fn renderer(&self) -> &dyn Renderer {
194 &self.renderer
195 }
196
197 fn size(&self) -> PhysicalSize {
198 self.size_impl()
199 }
200
201 fn set_size(&self, size: WindowSize) {
202 self.set_size_impl(size);
203 }
204
205 fn request_redraw(&self) {
206 RenderableWindow::request_redraw(self);
207 }
208}
209
210impl Deref for PopupWindow {
211 type Target = Window;
212 fn deref(&self) -> &Self::Target {
213 &self.window
214 }
215}
216
217impl Drop for PopupWindow {
218 fn drop(&mut self) {
219 info!("PopupWindow being dropped - cleaning up resources");
220
221 if let Some(component) = self.component_instance.borrow_mut().take() {
222 info!("Dropping any remaining ComponentInstance in PopupWindow::drop");
223 drop(component);
224 }
225
226 info!("PopupWindow drop complete");
227 }
228}