mirror of
https://codeberg.org/waydeer/layer-shika.git
synced 2025-11-04 18:44:24 +00:00
refactor: remove thread local global state and centralize popup state management
This commit is contained in:
parent
aa5052b566
commit
9808eeb819
6 changed files with 123 additions and 181 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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}");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue