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::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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18enum PopupRenderState {
19 Unconfigured,
21 Repositioning,
23 ReadyClean,
25 ReadyDirty,
27 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 return Ok(());
195 }
196 PopupRenderState::ReadyDirty | PopupRenderState::NeedsRelayout => {
197 }
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}