refactor: extract popup builder and consolidate popup config

This commit is contained in:
drendog 2025-11-26 02:07:51 +01:00
parent 30c3100ca2
commit 0756fc0ef7
Signed by: dwenya
GPG key ID: 8DD77074645332D0
4 changed files with 343 additions and 33 deletions

View file

@ -1,7 +1,9 @@
#![allow(clippy::pub_use)]
mod builder;
mod popup_builder;
mod system;
mod value_conversion;
use layer_shika_adapters::errors::LayerShikaError;
use layer_shika_domain::errors::DomainError;
@ -21,6 +23,7 @@ pub use layer_shika_domain::value_objects::popup_positioning_mode::PopupPosition
pub use layer_shika_domain::value_objects::popup_request::{
PopupAt, PopupHandle, PopupRequest, PopupSize,
};
pub use popup_builder::PopupBuilder;
pub use system::{App, EventLoopHandle, ShellContext, ShellControl};
pub mod calloop {
@ -47,8 +50,8 @@ pub mod prelude {
pub use crate::{
AnchorEdges, App, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika,
OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry, PopupAt,
PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result,
ShellContext, ShellControl,
PopupBuilder, PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow,
Result, ShellContext, ShellControl,
};
pub use crate::calloop::{Generic, Interest, Mode, PostAction, RegistrationToken, Timer};

View file

@ -0,0 +1,216 @@
use crate::Result;
use crate::system::App;
use layer_shika_adapters::platform::slint_interpreter::Value;
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
use layer_shika_domain::value_objects::popup_request::{PopupAt, PopupRequest, PopupSize};
pub struct PopupBuilder<'a> {
app: &'a App,
component: String,
reference: PopupAt,
anchor: PopupPositioningMode,
size: PopupSize,
grab: bool,
close_callback: Option<String>,
resize_callback: Option<String>,
}
impl<'a> PopupBuilder<'a> {
pub(crate) fn new(app: &'a App, component: String) -> Self {
Self {
app,
component,
reference: PopupAt::Cursor,
anchor: PopupPositioningMode::TopLeft,
size: PopupSize::Content,
grab: false,
close_callback: None,
resize_callback: None,
}
}
#[must_use]
pub fn relative_to_cursor(mut self) -> Self {
self.reference = PopupAt::Cursor;
self
}
#[must_use]
pub fn relative_to_point(mut self, x: f32, y: f32) -> Self {
self.reference = PopupAt::Absolute { x, y };
self
}
#[must_use]
pub fn relative_to_rect(mut self, x: f32, y: f32, w: f32, h: f32) -> Self {
self.reference = PopupAt::AnchorRect { x, y, w, h };
self
}
#[must_use]
pub const fn anchor(mut self, anchor: PopupPositioningMode) -> Self {
self.anchor = anchor;
self
}
#[must_use]
pub fn anchor_top_left(mut self) -> Self {
self.anchor = PopupPositioningMode::TopLeft;
self
}
#[must_use]
pub fn anchor_top_center(mut self) -> Self {
self.anchor = PopupPositioningMode::TopCenter;
self
}
#[must_use]
pub fn anchor_top_right(mut self) -> Self {
self.anchor = PopupPositioningMode::TopRight;
self
}
#[must_use]
pub fn anchor_center_left(mut self) -> Self {
self.anchor = PopupPositioningMode::CenterLeft;
self
}
#[must_use]
pub fn anchor_center(mut self) -> Self {
self.anchor = PopupPositioningMode::Center;
self
}
#[must_use]
pub fn anchor_center_right(mut self) -> Self {
self.anchor = PopupPositioningMode::CenterRight;
self
}
#[must_use]
pub fn anchor_bottom_left(mut self) -> Self {
self.anchor = PopupPositioningMode::BottomLeft;
self
}
#[must_use]
pub fn anchor_bottom_center(mut self) -> Self {
self.anchor = PopupPositioningMode::BottomCenter;
self
}
#[must_use]
pub fn anchor_bottom_right(mut self) -> Self {
self.anchor = PopupPositioningMode::BottomRight;
self
}
#[must_use]
pub const fn size(mut self, size: PopupSize) -> Self {
self.size = size;
self
}
#[must_use]
pub fn fixed_size(mut self, w: f32, h: f32) -> Self {
self.size = PopupSize::Fixed { w, h };
self
}
#[must_use]
pub fn content_size(mut self) -> Self {
self.size = PopupSize::Content;
self
}
#[must_use]
pub const fn grab(mut self, enable: bool) -> Self {
self.grab = enable;
self
}
#[must_use]
pub fn close_on(mut self, callback_name: impl Into<String>) -> Self {
self.close_callback = Some(callback_name.into());
self
}
#[must_use]
pub fn resize_on(mut self, callback_name: impl Into<String>) -> Self {
self.resize_callback = Some(callback_name.into());
self
}
pub fn bind(self, trigger_callback: &str) -> Result<()> {
let request = self.build_request();
let control = self.app.control();
self.app.with_all_component_instances(|instance| {
let request_clone = request.clone();
let control_clone = control.clone();
if let Err(e) = instance.set_callback(trigger_callback, move |_args| {
if let Err(e) = control_clone.show_popup(&request_clone) {
log::error!("Failed to show popup: {}", e);
}
Value::Void
}) {
log::error!(
"Failed to bind popup callback '{}': {}",
trigger_callback,
e
);
}
});
Ok(())
}
pub fn toggle(self, trigger_callback: &str) -> Result<()> {
let request = self.build_request();
let control = self.app.control();
let component_name = request.component.clone();
self.app.with_all_component_instances(|instance| {
let request_clone = request.clone();
let control_clone = control.clone();
let component_clone = component_name.clone();
if let Err(e) = instance.set_callback(trigger_callback, move |_args| {
log::debug!("Toggle callback for component: {}", component_clone);
if let Err(e) = control_clone.show_popup(&request_clone) {
log::error!("Failed to toggle popup: {}", e);
}
Value::Void
}) {
log::error!(
"Failed to bind toggle popup callback '{}': {}",
trigger_callback,
e
);
}
});
Ok(())
}
fn build_request(&self) -> PopupRequest {
let mut builder = PopupRequest::builder(self.component.clone())
.at(self.reference)
.size(self.size)
.mode(self.anchor)
.grab(self.grab);
if let Some(ref close_cb) = self.close_callback {
builder = builder.close_on(close_cb.clone());
}
if let Some(ref resize_cb) = self.resize_callback {
builder = builder.resize_on(resize_cb.clone());
}
builder.build()
}
}

View file

@ -1,3 +1,5 @@
use crate::popup_builder::PopupBuilder;
use crate::value_conversion::IntoValue;
use crate::{Error, Result};
use layer_shika_adapters::errors::EventLoopError;
use layer_shika_adapters::platform::calloop::{
@ -14,10 +16,13 @@ use layer_shika_adapters::{
use layer_shika_domain::config::WindowConfig;
use layer_shika_domain::entities::output_registry::OutputRegistry;
use layer_shika_domain::errors::DomainError;
use layer_shika_domain::value_objects::dimensions::PopupDimensions;
use layer_shika_domain::value_objects::output_handle::OutputHandle;
use layer_shika_domain::value_objects::output_info::OutputInfo;
use layer_shika_domain::value_objects::dimensions::PopupDimensions;
use layer_shika_domain::value_objects::popup_request::{PopupHandle, PopupRequest, PopupSize};
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
use layer_shika_domain::value_objects::popup_request::{
PopupAt, PopupHandle, PopupRequest, PopupSize,
};
use std::cell::Cell;
use std::cell::RefCell;
use std::os::unix::io::AsFd;
@ -51,10 +56,35 @@ impl ShellControl {
})
}
pub fn show_popup_at_cursor(&self, component: impl Into<String>) -> Result<()> {
let request = PopupRequest::builder(component.into())
.at(PopupAt::Cursor)
.build();
self.show_popup(&request)
}
pub fn show_popup_centered(&self, component: impl Into<String>) -> Result<()> {
let request = PopupRequest::builder(component.into())
.at(PopupAt::Cursor)
.mode(PopupPositioningMode::Center)
.build();
self.show_popup(&request)
}
pub fn show_popup_at_position(
&self,
component: impl Into<String>,
x: f32,
y: f32,
) -> Result<()> {
let request = PopupRequest::builder(component.into())
.at(PopupAt::Absolute { x, y })
.build();
self.show_popup(&request)
}
pub fn close_popup(&self, handle: PopupHandle) -> Result<()> {
self.sender
.send(PopupCommand::Close(handle))
.map_err(|_| {
self.sender.send(PopupCommand::Close(handle)).map_err(|_| {
Error::Domain(DomainError::Configuration {
message: "Failed to send popup close command: channel closed".to_string(),
})
@ -547,7 +577,11 @@ impl ShellContext<'_> {
);
if control
.resize_popup(PopupHandle::new(popup_key), dimensions.width, dimensions.height)
.resize_popup(
PopupHandle::new(popup_key),
dimensions.width,
dimensions.height,
)
.is_err()
{
log::error!("Failed to resize popup through control");
@ -659,7 +693,8 @@ impl App {
match command {
PopupCommand::Show(request) => {
if let Err(e) = shell_context.show_popup(&request, Some(control.clone()))
if let Err(e) =
shell_context.show_popup(&request, Some(control.clone()))
{
log::error!("Failed to show popup: {}", e);
}
@ -674,9 +709,12 @@ impl App {
width,
height,
} => {
if let Err(e) =
shell_context.resize_popup(handle, width, height, Some(control.clone()))
{
if let Err(e) = shell_context.resize_popup(
handle,
width,
height,
Some(control.clone()),
) {
log::error!("Failed to resize popup: {}", e);
}
}
@ -709,17 +747,18 @@ impl App {
}
}
pub fn on_callback<F>(&self, callback_name: &str, handler: F) -> Result<()>
pub fn on<F, R>(&self, callback_name: &str, handler: F) -> Result<()>
where
F: Fn(&[Value], ShellControl) -> Value + 'static,
F: Fn(ShellControl) -> R + 'static,
R: IntoValue,
{
let control = self.control();
let handler = Rc::new(handler);
self.with_all_component_instances(|instance| {
let handler_rc = Rc::clone(&handler);
let control_clone = control.clone();
if let Err(e) = instance.set_callback(callback_name, move |args| {
handler_rc(args, control_clone.clone())
if let Err(e) = instance.set_callback(callback_name, move |_args| {
handler_rc(control_clone.clone()).into_value()
}) {
log::error!(
"Failed to register callback '{}' on component: {}",
@ -731,35 +770,34 @@ impl App {
Ok(())
}
pub fn bind_popup<F>(&self, callback_name: &str, config_builder: F) -> Result<()>
pub fn on_with_args<F, R>(&self, callback_name: &str, handler: F) -> Result<()>
where
F: Fn() -> PopupRequest + 'static,
F: Fn(&[Value], ShellControl) -> R + 'static,
R: IntoValue,
{
let control = self.control();
let builder = Rc::new(config_builder);
let handler = Rc::new(handler);
self.with_all_component_instances(|instance| {
let builder_clone = Rc::clone(&builder);
let handler_rc = Rc::clone(&handler);
let control_clone = control.clone();
if let Err(e) = instance.set_callback(callback_name, move |_args| {
let request = builder_clone();
if let Err(e) = control_clone.show_popup(&request) {
log::error!("Failed to show popup: {}", e);
}
Value::Void
if let Err(e) = instance.set_callback(callback_name, move |args| {
handler_rc(args, control_clone.clone()).into_value()
}) {
log::error!(
"Failed to bind popup callback '{}': {}",
"Failed to register callback '{}' on component: {}",
callback_name,
e
);
}
});
Ok(())
}
#[must_use]
pub fn popup(&self, component_name: impl Into<String>) -> PopupBuilder<'_> {
PopupBuilder::new(self, component_name.into())
}
pub fn run(&mut self) -> Result<()> {
self.inner.borrow_mut().run()?;
Ok(())

View file

@ -0,0 +1,53 @@
use layer_shika_adapters::platform::slint_interpreter::Value;
pub trait IntoValue {
fn into_value(self) -> Value;
}
impl IntoValue for () {
fn into_value(self) -> Value {
Value::Void
}
}
impl IntoValue for Value {
fn into_value(self) -> Value {
self
}
}
impl IntoValue for bool {
fn into_value(self) -> Value {
Value::Bool(self)
}
}
impl IntoValue for i32 {
fn into_value(self) -> Value {
Value::Number(f64::from(self))
}
}
impl IntoValue for f32 {
fn into_value(self) -> Value {
Value::Number(f64::from(self))
}
}
impl IntoValue for f64 {
fn into_value(self) -> Value {
Value::Number(self)
}
}
impl IntoValue for String {
fn into_value(self) -> Value {
Value::String(self.into())
}
}
impl IntoValue for &str {
fn into_value(self) -> Value {
Value::String(self.into())
}
}