refactor: remove thread local global state and centralize popup state management

This commit is contained in:
drendog 2025-11-03 16:06:10 +01:00
parent aa5052b566
commit 9808eeb819
Signed by: dwenya
GPG key ID: 8DD77074645332D0
6 changed files with 123 additions and 181 deletions

View file

@ -5,9 +5,6 @@ pub mod rendering;
pub mod wayland;
pub use rendering::femtovg::popup_window::PopupWindow;
pub use rendering::slint_integration::platform::{
clear_popup_config, close_current_popup, get_popup_config, set_popup_config,
};
pub mod platform {
pub use slint;

View file

@ -1,4 +1,3 @@
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
use slint::{
PlatformError,
platform::{Platform, WindowAdapter},
@ -7,134 +6,31 @@ use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::rendering::femtovg::popup_window::PopupWindow;
type PopupCreator = dyn Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError>;
type PopupConfigData = (f32, f32, f32, f32, PopupPositioningMode);
thread_local! {
static CURRENT_PLATFORM: RefCell<Option<Weak<CustomSlintPlatform>>> = const { RefCell::new(None) };
}
pub fn close_current_popup() {
CURRENT_PLATFORM.with(|platform| {
if let Some(weak_platform) = platform.borrow().as_ref() {
if let Some(strong_platform) = weak_platform.upgrade() {
strong_platform.close_current_popup();
}
}
});
}
pub fn set_popup_config(
reference_x: f32,
reference_y: f32,
width: f32,
height: f32,
positioning_mode: PopupPositioningMode,
) {
CURRENT_PLATFORM.with(|platform| {
if let Some(weak_platform) = platform.borrow().as_ref() {
if let Some(strong_platform) = weak_platform.upgrade() {
strong_platform.set_popup_config(
reference_x,
reference_y,
width,
height,
positioning_mode,
);
}
}
});
}
pub fn get_popup_config() -> Option<PopupConfigData> {
CURRENT_PLATFORM.with(|platform| {
platform
.borrow()
.as_ref()
.and_then(Weak::upgrade)
.and_then(|strong| strong.get_popup_config())
})
}
pub fn clear_popup_config() {
CURRENT_PLATFORM.with(|platform| {
if let Some(weak_platform) = platform.borrow().as_ref() {
if let Some(strong_platform) = weak_platform.upgrade() {
strong_platform.clear_popup_config();
}
}
});
}
pub struct CustomSlintPlatform {
main_window: Weak<FemtoVGWindow>,
popup_creator: RefCell<Option<Rc<PopupCreator>>>,
first_call: Cell<bool>,
last_popup: RefCell<Option<Weak<PopupWindow>>>,
popup_config: RefCell<Option<PopupConfigData>>,
}
impl CustomSlintPlatform {
#[must_use]
pub fn new(window: &Rc<FemtoVGWindow>) -> Rc<Self> {
let platform = Rc::new(Self {
Rc::new(Self {
main_window: Rc::downgrade(window),
popup_creator: RefCell::new(None),
first_call: Cell::new(true),
last_popup: RefCell::new(None),
popup_config: RefCell::new(None),
});
CURRENT_PLATFORM.with(|current| {
*current.borrow_mut() = Some(Rc::downgrade(&platform));
});
platform
})
}
#[allow(dead_code)]
pub fn set_popup_creator<F>(&self, creator: F)
where
F: Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError> + 'static,
{
*self.popup_creator.borrow_mut() = Some(Rc::new(creator));
}
pub fn set_last_popup(&self, popup: &Rc<PopupWindow>) {
*self.last_popup.borrow_mut() = Some(Rc::downgrade(popup));
}
pub fn close_current_popup(&self) {
if let Some(weak_popup) = self.last_popup.borrow().as_ref() {
if let Some(popup) = weak_popup.upgrade() {
popup.close_popup();
}
}
*self.last_popup.borrow_mut() = None;
}
pub fn set_popup_config(
&self,
reference_x: f32,
reference_y: f32,
width: f32,
height: f32,
positioning_mode: PopupPositioningMode,
) {
*self.popup_config.borrow_mut() =
Some((reference_x, reference_y, width, height, positioning_mode));
}
#[must_use]
pub fn get_popup_config(&self) -> Option<PopupConfigData> {
*self.popup_config.borrow()
}
pub fn clear_popup_config(&self) {
*self.popup_config.borrow_mut() = None;
}
}
impl Platform for CustomSlintPlatform {

View file

@ -11,9 +11,8 @@ use crate::wayland::{
use crate::{
errors::{EventLoopError, LayerShikaError, RenderingError, Result},
rendering::{
egl::context::EGLContext,
femtovg::main_window::FemtoVGWindow,
slint_integration::platform::{CustomSlintPlatform, clear_popup_config, get_popup_config},
egl::context::EGLContext, femtovg::main_window::FemtoVGWindow,
slint_integration::platform::CustomSlintPlatform,
},
};
use core::result::Result as CoreResult;
@ -172,7 +171,6 @@ impl WaylandWindowingSystem {
info!("Setting up popup creator with xdg-shell support");
let popup_manager_clone = Rc::clone(popup_manager);
let platform_weak = Rc::downgrade(platform);
let layer_surface = state.layer_surface();
let queue_handle = event_queue.handle();
let serial_holder = Rc::clone(shared_serial);
@ -182,45 +180,37 @@ impl WaylandWindowingSystem {
let serial = serial_holder.get();
let params = popup_manager_clone.take_pending_popup_config();
let params = if let Some(mut p) = params {
p.last_pointer_serial = serial;
p
} else {
let output_size = popup_manager_clone.output_size();
#[allow(clippy::cast_precision_loss)]
let default_width = output_size.width as f32;
#[allow(clippy::cast_precision_loss)]
let default_height = output_size.height as f32;
let (reference_x, reference_y, width, height, positioning_mode) = get_popup_config()
.unwrap_or_else(|| {
log::warn!("No popup config provided, using output size ({default_width}x{default_height}) as defaults");
(
0.0,
0.0,
default_width,
default_height,
PopupPositioningMode::TopLeft,
)
});
clear_popup_config();
CreatePopupParams {
last_pointer_serial: serial,
reference_x: 0.0,
reference_y: 0.0,
width: default_width,
height: default_height,
positioning_mode: PopupPositioningMode::TopLeft,
}
};
let popup_window = popup_manager_clone
.create_popup(
&queue_handle,
&layer_surface,
CreatePopupParams {
last_pointer_serial: serial,
reference_x,
reference_y,
width,
height,
positioning_mode,
},
params,
)
.map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")))?;
if let Some(platform) = platform_weak.upgrade() {
platform.set_last_popup(&popup_window);
}
let result = Ok(popup_window as Rc<dyn WindowAdapter>);
match &result {

View file

@ -72,6 +72,8 @@ pub struct PopupManager {
popups: RefCell<Slab<ActivePopup>>,
current_scale_factor: RefCell<f32>,
current_output_size: RefCell<PhysicalSize>,
pending_popup_config: RefCell<Option<CreatePopupParams>>,
last_popup_key: RefCell<Option<usize>>,
}
impl PopupManager {
@ -82,6 +84,39 @@ impl PopupManager {
popups: RefCell::new(Slab::new()),
current_scale_factor: RefCell::new(initial_scale_factor),
current_output_size: RefCell::new(PhysicalSize::new(0, 0)),
pending_popup_config: RefCell::new(None),
last_popup_key: RefCell::new(None),
}
}
pub fn set_pending_popup_config(
&self,
reference_x: f32,
reference_y: f32,
width: f32,
height: f32,
positioning_mode: PopupPositioningMode,
) {
let last_pointer_serial = 0;
*self.pending_popup_config.borrow_mut() = Some(CreatePopupParams {
last_pointer_serial,
reference_x,
reference_y,
width,
height,
positioning_mode,
});
}
#[must_use]
pub fn take_pending_popup_config(&self) -> Option<CreatePopupParams> {
self.pending_popup_config.borrow_mut().take()
}
pub fn close_current_popup(&self) {
let key = self.last_popup_key.borrow_mut().take();
if let Some(key) = key {
self.destroy_popup(key);
}
}
@ -171,6 +206,7 @@ impl PopupManager {
window: Rc::clone(&popup_window),
});
popup_window.set_popup_manager(Rc::downgrade(self), key);
*self.last_popup_key.borrow_mut() = Some(key);
info!("Popup window created successfully with key {key}");

View file

@ -9,9 +9,7 @@ use std::result::Result as StdResult;
pub use builder::LayerShika;
pub use layer_shika_adapters::PopupWindow;
pub use layer_shika_adapters::close_current_popup;
pub use layer_shika_adapters::platform::{slint, slint_interpreter};
pub use layer_shika_adapters::{clear_popup_config, get_popup_config, set_popup_config};
pub use layer_shika_domain::value_objects::anchor::AnchorEdges;
pub use layer_shika_domain::value_objects::keyboard_interactivity::KeyboardInteractivity;
pub use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;

View file

@ -12,7 +12,6 @@ use layer_shika_adapters::wayland::{
config::WaylandWindowConfig, shell_adapter::WaylandWindowingSystem,
surfaces::surface_state::WindowState,
};
use layer_shika_adapters::{clear_popup_config, close_current_popup, set_popup_config};
use layer_shika_domain::config::WindowConfig;
use layer_shika_domain::errors::DomainError;
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
@ -129,8 +128,15 @@ impl RuntimeState<'_> {
self.window_state.compilation_result()
}
pub fn close_current_popup(&mut self) -> Result<()> {
if let Some(popup_manager) = self.window_state.popup_manager() {
popup_manager.close_current_popup();
}
Ok(())
}
pub fn show_popup_component(
&self,
&mut self,
component_name: &str,
position: Option<(f32, f32)>,
size: Option<(f32, f32)>,
@ -153,16 +159,20 @@ impl RuntimeState<'_> {
})
})?;
close_current_popup();
self.close_current_popup()?;
let (width, height) = if let Some(explicit_size) = size {
explicit_size
} else {
let temp_instance = definition.create().map_err(|e| {
Error::Domain(DomainError::Configuration {
message: format!("Failed to create temporary popup instance: {}", e),
})
})?;
temp_instance.hide().map_err(|e| {
temp_instance.show().map_err(|e| {
Error::Domain(DomainError::Configuration {
message: format!("Failed to hide temporary popup instance: {}", e),
message: format!("Failed to show temporary popup instance: {}", e),
})
})?;
@ -170,24 +180,38 @@ impl RuntimeState<'_> {
.get_property("popup-width")
.ok()
.and_then(|v| v.try_into().ok())
.or(size.map(|(w, _)| w))
.unwrap_or(300.0);
.unwrap_or(120.0);
let height: f32 = temp_instance
.get_property("popup-height")
.ok()
.and_then(|v| v.try_into().ok())
.or(size.map(|(_, h)| h))
.unwrap_or(400.0);
.unwrap_or(120.0);
drop(temp_instance);
close_current_popup();
self.close_current_popup()?;
if let Some((reference_x, reference_y)) = position {
set_popup_config(reference_x, reference_y, width, height, positioning_mode);
} else {
clear_popup_config();
}
(width, height)
};
let popup_manager = self
.window_state
.popup_manager()
.as_ref()
.ok_or_else(|| Error::Domain(DomainError::Configuration {
message: "No popup manager available".to_string(),
}))
.map(Rc::clone)?;
let (reference_x, reference_y) = position.unwrap_or((0.0, 0.0));
popup_manager.set_pending_popup_config(
reference_x,
reference_y,
width,
height,
positioning_mode,
);
let instance = definition.create().map_err(|e| {
Error::Domain(DomainError::Configuration {
@ -195,9 +219,10 @@ impl RuntimeState<'_> {
})
})?;
let popup_manager_for_callback = Rc::clone(&popup_manager);
instance
.set_callback("closed", move |_| {
close_current_popup();
popup_manager_for_callback.close_current_popup();
Value::Void
})
.map_err(|e| {
@ -276,7 +301,7 @@ impl WindowingSystem {
let popup_mode_for_channel = Rc::clone(&self.popup_positioning_mode);
let (_token, sender) = event_loop_handle.add_channel(
move |(component_name, x, y): (String, f32, f32), state| {
move |(component_name, x, y): (String, f32, f32), mut state| {
let mode = *popup_mode_for_channel.borrow();
if let Err(e) =
state.show_popup_component(&component_name, Some((x, y)), None, mode)