mirror of
https://codeberg.org/waydeer/layer-shika.git
synced 2025-12-23 10:25:54 +00:00
272 lines
8.3 KiB
Rust
272 lines
8.3 KiB
Rust
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<RenderState>,
|
|
size: Cell<PhysicalSize>,
|
|
scale_factor: Cell<f32>,
|
|
popup_handle: Cell<Option<PopupHandle>>,
|
|
on_close: OnceCell<OnCloseCallback>,
|
|
popup_render_state: Cell<PopupRenderState>,
|
|
component_instance: RefCell<Option<ComponentInstance>>,
|
|
}
|
|
|
|
impl PopupWindow {
|
|
#[must_use]
|
|
pub fn new(renderer: FemtoVGRenderer) -> Rc<Self> {
|
|
Rc::new_cyclic(|weak_self| {
|
|
let window = Window::new(Weak::clone(weak_self) as Weak<dyn WindowAdapter>);
|
|
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<Self> {
|
|
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<usize> {
|
|
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<RenderState> {
|
|
&self.render_state
|
|
}
|
|
|
|
fn size_cell(&self) -> &Cell<PhysicalSize> {
|
|
&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");
|
|
}
|
|
}
|