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 mod wayland;
pub use rendering::femtovg::popup_window::PopupWindow; 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 mod platform {
pub use slint; pub use slint;

View file

@ -1,4 +1,3 @@
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
use slint::{ use slint::{
PlatformError, PlatformError,
platform::{Platform, WindowAdapter}, platform::{Platform, WindowAdapter},
@ -7,134 +6,31 @@ use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use crate::rendering::femtovg::main_window::FemtoVGWindow; use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::rendering::femtovg::popup_window::PopupWindow;
type PopupCreator = dyn Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError>; 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 { pub struct CustomSlintPlatform {
main_window: Weak<FemtoVGWindow>, main_window: Weak<FemtoVGWindow>,
popup_creator: RefCell<Option<Rc<PopupCreator>>>, popup_creator: RefCell<Option<Rc<PopupCreator>>>,
first_call: Cell<bool>, first_call: Cell<bool>,
last_popup: RefCell<Option<Weak<PopupWindow>>>,
popup_config: RefCell<Option<PopupConfigData>>,
} }
impl CustomSlintPlatform { impl CustomSlintPlatform {
#[must_use] #[must_use]
pub fn new(window: &Rc<FemtoVGWindow>) -> Rc<Self> { pub fn new(window: &Rc<FemtoVGWindow>) -> Rc<Self> {
let platform = Rc::new(Self { Rc::new(Self {
main_window: Rc::downgrade(window), main_window: Rc::downgrade(window),
popup_creator: RefCell::new(None), popup_creator: RefCell::new(None),
first_call: Cell::new(true), 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) pub fn set_popup_creator<F>(&self, creator: F)
where where
F: Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError> + 'static, F: Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError> + 'static,
{ {
*self.popup_creator.borrow_mut() = Some(Rc::new(creator)); *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 { impl Platform for CustomSlintPlatform {

View file

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

View file

@ -72,6 +72,8 @@ pub struct PopupManager {
popups: RefCell<Slab<ActivePopup>>, popups: RefCell<Slab<ActivePopup>>,
current_scale_factor: RefCell<f32>, current_scale_factor: RefCell<f32>,
current_output_size: RefCell<PhysicalSize>, current_output_size: RefCell<PhysicalSize>,
pending_popup_config: RefCell<Option<CreatePopupParams>>,
last_popup_key: RefCell<Option<usize>>,
} }
impl PopupManager { impl PopupManager {
@ -82,6 +84,39 @@ impl PopupManager {
popups: RefCell::new(Slab::new()), popups: RefCell::new(Slab::new()),
current_scale_factor: RefCell::new(initial_scale_factor), current_scale_factor: RefCell::new(initial_scale_factor),
current_output_size: RefCell::new(PhysicalSize::new(0, 0)), 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), window: Rc::clone(&popup_window),
}); });
popup_window.set_popup_manager(Rc::downgrade(self), key); 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}"); 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 builder::LayerShika;
pub use layer_shika_adapters::PopupWindow; 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::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::anchor::AnchorEdges;
pub use layer_shika_domain::value_objects::keyboard_interactivity::KeyboardInteractivity; pub use layer_shika_domain::value_objects::keyboard_interactivity::KeyboardInteractivity;
pub use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; 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, config::WaylandWindowConfig, shell_adapter::WaylandWindowingSystem,
surfaces::surface_state::WindowState, 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::config::WindowConfig;
use layer_shika_domain::errors::DomainError; use layer_shika_domain::errors::DomainError;
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
@ -129,8 +128,15 @@ impl RuntimeState<'_> {
self.window_state.compilation_result() 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( pub fn show_popup_component(
&self, &mut self,
component_name: &str, component_name: &str,
position: Option<(f32, f32)>, position: Option<(f32, f32)>,
size: 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| { let temp_instance = definition.create().map_err(|e| {
Error::Domain(DomainError::Configuration { Error::Domain(DomainError::Configuration {
message: format!("Failed to create temporary popup instance: {}", e), 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 { 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") .get_property("popup-width")
.ok() .ok()
.and_then(|v| v.try_into().ok()) .and_then(|v| v.try_into().ok())
.or(size.map(|(w, _)| w)) .unwrap_or(120.0);
.unwrap_or(300.0);
let height: f32 = temp_instance let height: f32 = temp_instance
.get_property("popup-height") .get_property("popup-height")
.ok() .ok()
.and_then(|v| v.try_into().ok()) .and_then(|v| v.try_into().ok())
.or(size.map(|(_, h)| h)) .unwrap_or(120.0);
.unwrap_or(400.0);
drop(temp_instance); drop(temp_instance);
close_current_popup(); self.close_current_popup()?;
if let Some((reference_x, reference_y)) = position { (width, height)
set_popup_config(reference_x, reference_y, width, height, positioning_mode); };
} else {
clear_popup_config(); 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| { let instance = definition.create().map_err(|e| {
Error::Domain(DomainError::Configuration { Error::Domain(DomainError::Configuration {
@ -195,9 +219,10 @@ impl RuntimeState<'_> {
}) })
})?; })?;
let popup_manager_for_callback = Rc::clone(&popup_manager);
instance instance
.set_callback("closed", move |_| { .set_callback("closed", move |_| {
close_current_popup(); popup_manager_for_callback.close_current_popup();
Value::Void Value::Void
}) })
.map_err(|e| { .map_err(|e| {
@ -276,7 +301,7 @@ impl WindowingSystem {
let popup_mode_for_channel = Rc::clone(&self.popup_positioning_mode); let popup_mode_for_channel = Rc::clone(&self.popup_positioning_mode);
let (_token, sender) = event_loop_handle.add_channel( 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(); let mode = *popup_mode_for_channel.borrow();
if let Err(e) = if let Err(e) =
state.show_popup_component(&component_name, Some((x, y)), None, mode) state.show_popup_component(&component_name, Some((x, y)), None, mode)