feat: add popup abstraction

This commit is contained in:
drendog 2025-11-03 15:06:24 +01:00
parent bc46570971
commit aa5052b566
Signed by: dwenya
GPG key ID: 8DD77074645332D0
3 changed files with 180 additions and 3 deletions

1
Cargo.lock generated
View file

@ -1857,6 +1857,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"layer-shika-adapters", "layer-shika-adapters",
"layer-shika-domain", "layer-shika-domain",
"log",
"spin_on", "spin_on",
"thiserror 2.0.17", "thiserror 2.0.17",
] ]

View file

@ -16,5 +16,6 @@ workspace = true
[dependencies] [dependencies]
layer-shika-adapters.workspace = true layer-shika-adapters.workspace = true
layer-shika-domain.workspace = true layer-shika-domain.workspace = true
log.workspace = true
spin_on.workspace = true spin_on.workspace = true
thiserror.workspace = true thiserror.workspace = true

View file

@ -4,14 +4,18 @@ use layer_shika_adapters::platform::calloop::{
EventSource, Generic, Interest, Mode, PostAction, RegistrationToken, TimeoutAction, Timer, EventSource, Generic, Interest, Mode, PostAction, RegistrationToken, TimeoutAction, Timer,
channel, channel,
}; };
use layer_shika_adapters::platform::slint::ComponentHandle;
use layer_shika_adapters::platform::slint_interpreter::{ use layer_shika_adapters::platform::slint_interpreter::{
CompilationResult, ComponentDefinition, ComponentInstance, CompilationResult, ComponentDefinition, ComponentInstance, Value,
}; };
use layer_shika_adapters::wayland::{ 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::value_objects::popup_positioning_mode::PopupPositioningMode;
use std::cell::{Ref, RefCell}; use std::cell::{Ref, RefCell};
use std::os::unix::io::AsFd; use std::os::unix::io::AsFd;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
@ -124,10 +128,97 @@ impl RuntimeState<'_> {
pub fn compilation_result(&self) -> Option<Rc<CompilationResult>> { pub fn compilation_result(&self) -> Option<Rc<CompilationResult>> {
self.window_state.compilation_result() self.window_state.compilation_result()
} }
pub fn show_popup_component(
&self,
component_name: &str,
position: Option<(f32, f32)>,
size: Option<(f32, f32)>,
positioning_mode: PopupPositioningMode,
) -> Result<()> {
let compilation_result = self.compilation_result().ok_or_else(|| {
Error::Domain(DomainError::Configuration {
message: "No compilation result available for popup creation".to_string(),
})
})?;
let definition = compilation_result
.component(component_name)
.ok_or_else(|| {
Error::Domain(DomainError::Configuration {
message: format!(
"{} component not found in compilation result",
component_name
),
})
})?;
close_current_popup();
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| {
Error::Domain(DomainError::Configuration {
message: format!("Failed to hide temporary popup instance: {}", e),
})
})?;
let width: f32 = temp_instance
.get_property("popup-width")
.ok()
.and_then(|v| v.try_into().ok())
.or(size.map(|(w, _)| w))
.unwrap_or(300.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);
drop(temp_instance);
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();
}
let instance = definition.create().map_err(|e| {
Error::Domain(DomainError::Configuration {
message: format!("Failed to create popup instance: {}", e),
})
})?;
instance
.set_callback("closed", move |_| {
close_current_popup();
Value::Void
})
.map_err(|e| {
Error::Domain(DomainError::Configuration {
message: format!("Failed to set popup closed callback: {}", e),
})
})?;
instance.show().map_err(|e| {
Error::Domain(DomainError::Configuration {
message: format!("Failed to show popup instance: {}", e),
})
})?;
Ok(())
}
} }
pub struct WindowingSystem { pub struct WindowingSystem {
inner: Rc<RefCell<WaylandWindowingSystem>>, inner: Rc<RefCell<WaylandWindowingSystem>>,
popup_positioning_mode: Rc<RefCell<PopupPositioningMode>>,
} }
impl WindowingSystem { impl WindowingSystem {
@ -143,9 +234,93 @@ impl WindowingSystem {
); );
let inner = WaylandWindowingSystem::new(wayland_config)?; let inner = WaylandWindowingSystem::new(wayland_config)?;
Ok(Self { let system = Self {
inner: Rc::new(RefCell::new(inner)), inner: Rc::new(RefCell::new(inner)),
popup_positioning_mode: Rc::new(RefCell::new(PopupPositioningMode::Center)),
};
system.register_popup_callbacks()?;
Ok(system)
}
fn register_popup_callbacks(&self) -> Result<()> {
let component_instance = self.component_instance();
let popup_mode_clone = Rc::clone(&self.popup_positioning_mode);
component_instance
.set_callback("set_popup_positioning_mode", move |args| {
let center_x: bool = args
.first()
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(false);
let center_y: bool = args
.get(1)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(false);
let mode = PopupPositioningMode::from_flags(center_x, center_y);
*popup_mode_clone.borrow_mut() = mode;
Value::Void
}) })
.map_err(|e| {
Error::Domain(DomainError::Configuration {
message: format!(
"Failed to register set_popup_positioning_mode callback: {}",
e
),
})
})?;
let event_loop_handle = self.event_loop_handle();
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| {
let mode = *popup_mode_for_channel.borrow();
if let Err(e) =
state.show_popup_component(&component_name, Some((x, y)), None, mode)
{
log::error!("Failed to show popup: {}", e);
}
},
)?;
component_instance
.set_callback("show_popup", move |args| {
use layer_shika_adapters::platform::slint::SharedString;
let component_name: SharedString = args
.first()
.and_then(|v| v.clone().try_into().ok())
.unwrap_or_else(|| SharedString::from(""));
if component_name.is_empty() {
log::error!("show_popup called without component name");
return Value::Void;
}
let x: f32 = args
.get(1)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(0.0);
let y: f32 = args
.get(2)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(0.0);
if sender.send((component_name.to_string(), x, y)).is_err() {
log::error!("Failed to send popup request through channel");
}
Value::Void
})
.map_err(|e| {
Error::Domain(DomainError::Configuration {
message: format!("Failed to register show_popup callback: {}", e),
})
})?;
Ok(())
} }
#[must_use] #[must_use]