feat: add popup bind anchored

This commit is contained in:
drendog 2025-11-27 23:56:41 +01:00
parent 7f99f13c09
commit 64a18153b6
Signed by: dwenya
GPG key ID: 8DD77074645332D0
6 changed files with 159 additions and 7 deletions

View file

@ -13,6 +13,7 @@ pub use builder::LayerShika;
pub use layer_shika_adapters::PopupWindow;
pub use layer_shika_adapters::platform::{slint, slint_interpreter};
pub use layer_shika_domain::entities::output_registry::OutputRegistry;
pub use layer_shika_domain::prelude::AnchorStrategy;
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::layer::Layer;
@ -48,10 +49,10 @@ pub enum Error {
pub mod prelude {
pub use crate::{
AnchorEdges, App, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika,
OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry, PopupAt,
PopupBuilder, PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow,
Result, ShellContext, ShellControl,
AnchorEdges, AnchorStrategy, App, EventLoopHandle, KeyboardInteractivity, Layer,
LayerShika, OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry,
PopupAt, PopupBuilder, PopupHandle, PopupPositioningMode, PopupRequest, PopupSize,
PopupWindow, Result, ShellContext, ShellControl,
};
pub use crate::calloop::{Generic, Interest, Mode, PostAction, RegistrationToken, Timer};

View file

@ -1,6 +1,7 @@
use crate::Result;
use crate::system::App;
use layer_shika_adapters::platform::slint_interpreter::Value;
use layer_shika_domain::prelude::AnchorStrategy;
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
use layer_shika_domain::value_objects::popup_request::{PopupAt, PopupRequest, PopupSize};
@ -196,6 +197,104 @@ impl<'a> PopupBuilder<'a> {
Ok(())
}
pub fn bind_anchored(self, trigger_callback: &str, strategy: AnchorStrategy) -> Result<()> {
let component_name = self.component.clone();
let grab = self.grab;
let close_callback = self.close_callback.clone();
let resize_callback = self.resize_callback.clone();
let control = self.app.control();
self.app.with_all_component_instances(|instance| {
let component_clone = component_name.clone();
let control_clone = control.clone();
let close_cb = close_callback.clone();
let resize_cb = resize_callback.clone();
if let Err(e) = instance.set_callback(trigger_callback, move |args| {
if args.len() < 4 {
log::error!(
"bind_anchored callback expects 4 arguments (x, y, width, height), got {}",
args.len()
);
return Value::Void;
}
let anchor_x = args
.first()
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(0.0);
let anchor_y = args
.get(1)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(0.0);
let anchor_w = args
.get(2)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(0.0);
let anchor_h = args
.get(3)
.and_then(|v| v.clone().try_into().ok())
.unwrap_or(0.0);
log::debug!(
"Anchored popup triggered for '{}' at rect: ({}, {}, {}, {})",
component_clone,
anchor_x,
anchor_y,
anchor_w,
anchor_h
);
let mut builder = PopupRequest::builder(component_clone.clone())
.at(PopupAt::AnchorRect {
x: anchor_x,
y: anchor_y,
w: anchor_w,
h: anchor_h,
})
.size(PopupSize::Content)
.grab(grab);
let mode = match strategy {
AnchorStrategy::CenterBottom => PopupPositioningMode::TopCenter,
AnchorStrategy::CenterTop => PopupPositioningMode::BottomCenter,
AnchorStrategy::RightBottom => PopupPositioningMode::TopRight,
AnchorStrategy::LeftTop => PopupPositioningMode::BottomLeft,
AnchorStrategy::RightTop => PopupPositioningMode::BottomRight,
AnchorStrategy::LeftBottom | AnchorStrategy::Cursor => {
PopupPositioningMode::TopLeft
}
};
builder = builder.mode(mode);
if let Some(ref close_cb) = close_cb {
builder = builder.close_on(close_cb.clone());
}
if let Some(ref resize_cb) = resize_cb {
builder = builder.resize_on(resize_cb.clone());
}
let request = builder.build();
if let Err(e) = control_clone.show_popup(&request) {
log::error!("Failed to show anchored popup: {}", e);
}
Value::Void
}) {
log::error!(
"Failed to bind anchored popup callback '{}': {}",
trigger_callback,
e
);
}
});
Ok(())
}
fn build_request(&self) -> PopupRequest {
let mut builder = PopupRequest::builder(self.component.clone())
.at(self.reference)

View file

@ -8,6 +8,7 @@ pub use crate::entities::output_registry::OutputRegistry;
pub use crate::errors::{DomainError, Result};
pub use crate::surface_dimensions::SurfaceDimensions;
pub use crate::value_objects::anchor::AnchorEdges;
pub use crate::value_objects::anchor_strategy::AnchorStrategy;
pub use crate::value_objects::dimensions::{PopupDimensions, WindowHeight};
pub use crate::value_objects::keyboard_interactivity::KeyboardInteractivity;
pub use crate::value_objects::layer::Layer;

View file

@ -0,0 +1,49 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AnchorStrategy {
CenterBottom,
CenterTop,
LeftBottom,
RightBottom,
LeftTop,
RightTop,
Cursor,
}
impl AnchorStrategy {
#[must_use]
pub const fn calculate_position(
self,
anchor_x: f64,
anchor_y: f64,
anchor_w: f64,
anchor_h: f64,
popup_w: f64,
popup_h: f64,
) -> (f64, f64) {
match self {
Self::CenterBottom => {
let center_x = anchor_x + (anchor_w / 2.0);
let x = center_x - (popup_w / 2.0);
let y = anchor_y + anchor_h;
(x, y)
}
Self::CenterTop => {
let center_x = anchor_x + (anchor_w / 2.0);
let x = center_x - (popup_w / 2.0);
let y = anchor_y - popup_h;
(x, y)
}
Self::LeftBottom => (anchor_x, anchor_y + anchor_h),
Self::RightBottom => (anchor_x + anchor_w - popup_w, anchor_y + anchor_h),
Self::LeftTop => (anchor_x, anchor_y - popup_h),
Self::RightTop => (anchor_x + anchor_w - popup_w, anchor_y - popup_h),
Self::Cursor => (anchor_x, anchor_y),
}
}
}
impl Default for AnchorStrategy {
fn default() -> Self {
Self::CenterBottom
}
}

View file

@ -1,4 +1,5 @@
pub mod anchor;
pub mod anchor_strategy;
pub mod dimensions;
pub mod keyboard_interactivity;
pub mod layer;

View file

@ -42,9 +42,10 @@
pub mod prelude;
pub use layer_shika_composition::{
AnchorEdges, App, Error, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika,
OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry, PopupAt, PopupHandle,
PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, ShellContext, ShellControl,
AnchorEdges, AnchorStrategy, App, Error, EventLoopHandle, KeyboardInteractivity, Layer,
LayerShika, OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry, PopupAt,
PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, ShellContext,
ShellControl,
};
pub use layer_shika_composition::{slint, slint_interpreter};