mirror of
https://codeberg.org/waydeer/layer-shika.git
synced 2025-12-12 13:25:54 +00:00
feat: introducing base multi-window support
This commit is contained in:
parent
a2c376e8fc
commit
736d4d0905
22 changed files with 1154 additions and 121 deletions
|
|
@ -6,7 +6,7 @@ pub(crate) mod wayland;
|
|||
|
||||
pub use rendering::femtovg::popup_window::PopupWindow;
|
||||
|
||||
pub use wayland::config::WaylandWindowConfig;
|
||||
pub use wayland::config::{MultiWindowConfig, ShellWindowConfig, WaylandWindowConfig};
|
||||
pub use wayland::facade::WindowingSystemFacade;
|
||||
pub use wayland::shell_adapter::WaylandWindowingSystem;
|
||||
pub use wayland::surfaces::app_state::AppState;
|
||||
|
|
@ -25,4 +25,8 @@ pub mod platform {
|
|||
EventSource, InsertError, Interest, Mode, PostAction, RegistrationToken,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod wayland {
|
||||
pub use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::Anchor;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ pub(crate) struct LayerSurfaceConfig {
|
|||
pub exclusive_zone: i32,
|
||||
pub keyboard_interactivity: WaylandKeyboardInteractivity,
|
||||
pub height: u32,
|
||||
pub width: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WaylandWindowConfig {
|
||||
pub height: u32,
|
||||
pub width: u32,
|
||||
pub layer: zwlr_layer_shell_v1::Layer,
|
||||
pub margin: Margins,
|
||||
pub anchor: Anchor,
|
||||
|
|
@ -43,7 +45,8 @@ impl WaylandWindowConfig {
|
|||
domain_config: DomainWindowConfig,
|
||||
) -> Self {
|
||||
Self {
|
||||
height: domain_config.height.value(),
|
||||
height: domain_config.dimensions.height(),
|
||||
width: domain_config.dimensions.width(),
|
||||
layer: convert_layer(domain_config.layer),
|
||||
margin: domain_config.margin,
|
||||
anchor: convert_anchor(domain_config.anchor),
|
||||
|
|
@ -97,3 +100,37 @@ const fn convert_keyboard_interactivity(
|
|||
DomainKeyboardInteractivity::OnDemand => WaylandKeyboardInteractivity::OnDemand,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ShellWindowConfig {
|
||||
pub name: String,
|
||||
pub config: WaylandWindowConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MultiWindowConfig {
|
||||
pub windows: Vec<ShellWindowConfig>,
|
||||
pub compilation_result: Rc<CompilationResult>,
|
||||
}
|
||||
|
||||
impl MultiWindowConfig {
|
||||
pub fn new(compilation_result: Rc<CompilationResult>) -> Self {
|
||||
Self {
|
||||
windows: Vec::new(),
|
||||
compilation_result,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn add_window(mut self, name: impl Into<String>, config: WaylandWindowConfig) -> Self {
|
||||
self.windows.push(ShellWindowConfig {
|
||||
name: name.into(),
|
||||
config,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn primary_config(&self) -> Option<&WaylandWindowConfig> {
|
||||
self.windows.first().map(|w| &w.config)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ impl Dispatch<WlOutput, ()> for AppState {
|
|||
width, height, is_current, is_preferred
|
||||
);
|
||||
if is_current {
|
||||
if let Some(window) = state.get_output_by_output_id_mut(&output_id) {
|
||||
for window in state.all_windows_for_output_mut(&output_id) {
|
||||
window.handle_output_mode(width, height);
|
||||
}
|
||||
}
|
||||
|
|
@ -202,21 +202,16 @@ impl Dispatch<WlPointer, ()> for AppState {
|
|||
|
||||
let surface_id = surface.id();
|
||||
|
||||
if let Some(window) = state.get_output_by_surface_mut(&surface_id) {
|
||||
window.handle_pointer_enter(serial, &surface, surface_x, surface_y);
|
||||
|
||||
if let Some(handle) = state.get_handle_by_surface(&surface_id) {
|
||||
state.set_active_output_handle(Some(handle));
|
||||
}
|
||||
} else {
|
||||
let handle = state.get_handle_by_popup(&surface_id);
|
||||
if let Some(window) = state.find_output_by_popup_mut(&surface_id) {
|
||||
if let Some(key) = state.get_key_by_surface(&surface_id).cloned() {
|
||||
if let Some(window) = state.get_window_by_key_mut(&key) {
|
||||
window.handle_pointer_enter(serial, &surface, surface_x, surface_y);
|
||||
|
||||
if let Some(handle) = handle {
|
||||
state.set_active_output_handle(Some(handle));
|
||||
}
|
||||
}
|
||||
state.set_active_window_key(Some(key));
|
||||
} else if let Some(key) = state.get_key_by_popup(&surface_id).cloned() {
|
||||
if let Some(window) = state.get_window_by_key_mut(&key) {
|
||||
window.handle_pointer_enter(serial, &surface, surface_x, surface_y);
|
||||
}
|
||||
state.set_active_window_key(Some(key));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -225,20 +220,16 @@ impl Dispatch<WlPointer, ()> for AppState {
|
|||
surface_y,
|
||||
..
|
||||
} => {
|
||||
if let Some(output_handle) = state.active_output_handle() {
|
||||
if let Some(window) = state.get_output_by_handle_mut(output_handle) {
|
||||
window.handle_pointer_motion(surface_x, surface_y);
|
||||
}
|
||||
if let Some(window) = state.active_window_mut() {
|
||||
window.handle_pointer_motion(surface_x, surface_y);
|
||||
}
|
||||
}
|
||||
|
||||
wl_pointer::Event::Leave { .. } => {
|
||||
if let Some(output_handle) = state.active_output_handle() {
|
||||
if let Some(window) = state.get_output_by_handle_mut(output_handle) {
|
||||
window.handle_pointer_leave();
|
||||
}
|
||||
if let Some(window) = state.active_window_mut() {
|
||||
window.handle_pointer_leave();
|
||||
}
|
||||
state.set_active_output_handle(None);
|
||||
state.set_active_window_key(None);
|
||||
}
|
||||
|
||||
wl_pointer::Event::Button {
|
||||
|
|
@ -246,10 +237,8 @@ impl Dispatch<WlPointer, ()> for AppState {
|
|||
state: button_state,
|
||||
..
|
||||
} => {
|
||||
if let Some(output_handle) = state.active_output_handle() {
|
||||
if let Some(window) = state.get_output_by_handle_mut(output_handle) {
|
||||
window.handle_pointer_button(serial, button_state);
|
||||
}
|
||||
if let Some(window) = state.active_window_mut() {
|
||||
window.handle_pointer_button(serial, button_state);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ impl OutputManager {
|
|||
let (window, main_surface_id) =
|
||||
self.create_window_for_output(&pending_output.proxy, output_id, queue_handle)?;
|
||||
|
||||
app_state.add_output(output_id.clone(), main_surface_id, window);
|
||||
app_state.add_output(output_id, main_surface_id, window);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -182,6 +182,7 @@ impl OutputManager {
|
|||
.with_layer_surface(Rc::clone(&surface_ctx.layer_surface))
|
||||
.with_scale_factor(self.config.scale_factor)
|
||||
.with_height(self.config.height)
|
||||
.with_width(self.config.width)
|
||||
.with_exclusive_zone(self.config.exclusive_zone)
|
||||
.with_connection(Rc::clone(self.context.connection()))
|
||||
.with_pointer(Rc::clone(&self.context.pointer))
|
||||
|
|
@ -215,10 +216,14 @@ impl OutputManager {
|
|||
if let Some(handle) = self.output_mapping.remove(output_id) {
|
||||
info!("Removing output {handle:?} (id: {output_id:?})");
|
||||
|
||||
if let Some(_window) = app_state.remove_output(handle) {
|
||||
info!("Cleaned up window for output {handle:?}");
|
||||
} else {
|
||||
let removed_windows = app_state.remove_output(handle);
|
||||
if removed_windows.is_empty() {
|
||||
warn!("No window found for output handle {handle:?}");
|
||||
} else {
|
||||
info!(
|
||||
"Cleaned up {} window(s) for output {handle:?}",
|
||||
removed_windows.len()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.pending_outputs.borrow_mut().remove(output_id);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::wayland::{
|
||||
config::{LayerSurfaceConfig, WaylandWindowConfig},
|
||||
config::{LayerSurfaceConfig, ShellWindowConfig, WaylandWindowConfig},
|
||||
globals::context::GlobalContext,
|
||||
managed_proxies::ManagedWlPointer,
|
||||
outputs::{OutputManager, OutputManagerContext},
|
||||
|
|
@ -50,6 +50,7 @@ struct OutputSetup {
|
|||
main_surface_id: ObjectId,
|
||||
window: Rc<FemtoVGWindow>,
|
||||
builder: WindowStateBuilder,
|
||||
shell_window_name: String,
|
||||
}
|
||||
|
||||
struct OutputManagerParams<'a> {
|
||||
|
|
@ -87,6 +88,31 @@ impl WaylandWindowingSystem {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn new_multi(configs: &[ShellWindowConfig]) -> Result<Self> {
|
||||
if configs.is_empty() {
|
||||
return Err(LayerShikaError::InvalidInput {
|
||||
message: "At least one window config is required".into(),
|
||||
});
|
||||
}
|
||||
|
||||
info!(
|
||||
"Initializing WindowingSystem with {} window configs",
|
||||
configs.len()
|
||||
);
|
||||
let (connection, mut event_queue) = Self::init_wayland_connection()?;
|
||||
let event_loop =
|
||||
EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?;
|
||||
|
||||
let state = Self::init_state_multi(configs, &connection, &mut event_queue)?;
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
connection,
|
||||
event_queue,
|
||||
event_loop,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_wayland_connection() -> Result<(Rc<Connection>, EventQueue<AppState>)> {
|
||||
let connection = Rc::new(Connection::connect_to_env()?);
|
||||
let event_queue = connection.new_event_queue();
|
||||
|
|
@ -100,6 +126,7 @@ impl WaylandWindowingSystem {
|
|||
exclusive_zone: config.exclusive_zone,
|
||||
keyboard_interactivity: config.keyboard_interactivity,
|
||||
height: config.height,
|
||||
width: config.width,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,6 +179,7 @@ impl WaylandWindowingSystem {
|
|||
.with_layer_surface(Rc::clone(&surface_ctx.layer_surface))
|
||||
.with_scale_factor(config.scale_factor)
|
||||
.with_height(config.height)
|
||||
.with_width(config.width)
|
||||
.with_exclusive_zone(config.exclusive_zone)
|
||||
.with_connection(Rc::new(connection.clone()))
|
||||
.with_pointer(Rc::clone(pointer))
|
||||
|
|
@ -170,6 +198,7 @@ impl WaylandWindowingSystem {
|
|||
main_surface_id,
|
||||
window,
|
||||
builder,
|
||||
shell_window_name: "default".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +251,12 @@ impl WaylandWindowingSystem {
|
|||
popup_managers.push(Rc::clone(&popup_manager));
|
||||
layer_surfaces.push(per_output_window.layer_surface());
|
||||
|
||||
app_state.add_output(setup.output_id, setup.main_surface_id, per_output_window);
|
||||
app_state.add_shell_window(
|
||||
&setup.output_id,
|
||||
&setup.shell_window_name,
|
||||
setup.main_surface_id,
|
||||
per_output_window,
|
||||
);
|
||||
}
|
||||
|
||||
Ok((popup_managers, layer_surfaces))
|
||||
|
|
@ -295,6 +329,163 @@ impl WaylandWindowingSystem {
|
|||
Ok(app_state)
|
||||
}
|
||||
|
||||
fn init_state_multi(
|
||||
configs: &[ShellWindowConfig],
|
||||
connection: &Connection,
|
||||
event_queue: &mut EventQueue<AppState>,
|
||||
) -> Result<AppState> {
|
||||
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?;
|
||||
|
||||
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
|
||||
let shared_serial = Rc::new(SharedPointerSerial::new());
|
||||
|
||||
let mut app_state = AppState::new(
|
||||
ManagedWlPointer::new(Rc::clone(&pointer), Rc::new(connection.clone())),
|
||||
Rc::clone(&shared_serial),
|
||||
);
|
||||
|
||||
let render_factory =
|
||||
RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager));
|
||||
|
||||
let popup_context = PopupContext::new(
|
||||
global_ctx.compositor.clone(),
|
||||
global_ctx.xdg_wm_base.clone(),
|
||||
global_ctx.seat.clone(),
|
||||
global_ctx.fractional_scale_manager.clone(),
|
||||
global_ctx.viewporter.clone(),
|
||||
Rc::new(connection.clone()),
|
||||
Rc::clone(&render_factory),
|
||||
);
|
||||
|
||||
let setups = Self::create_output_setups_multi(
|
||||
configs,
|
||||
&global_ctx,
|
||||
connection,
|
||||
event_queue,
|
||||
&pointer,
|
||||
)?;
|
||||
|
||||
let platform = Self::setup_platform(&setups)?;
|
||||
|
||||
let (popup_managers, layer_surfaces) =
|
||||
Self::create_window_states(setups, &popup_context, &shared_serial, &mut app_state)?;
|
||||
|
||||
Self::setup_shared_popup_creator(
|
||||
popup_managers,
|
||||
layer_surfaces,
|
||||
&platform,
|
||||
&event_queue.handle(),
|
||||
&shared_serial,
|
||||
);
|
||||
|
||||
let primary_config = configs.first().map(|c| &c.config);
|
||||
if let Some(config) = primary_config {
|
||||
let layer_surface_config = Self::create_layer_surface_config(config);
|
||||
let output_manager = Self::create_output_manager(&OutputManagerParams {
|
||||
config,
|
||||
global_ctx: &global_ctx,
|
||||
connection,
|
||||
layer_surface_config,
|
||||
render_factory: &render_factory,
|
||||
popup_context: &popup_context,
|
||||
pointer: &pointer,
|
||||
shared_serial: &shared_serial,
|
||||
});
|
||||
|
||||
app_state.set_output_manager(Rc::new(RefCell::new(output_manager)));
|
||||
}
|
||||
|
||||
Ok(app_state)
|
||||
}
|
||||
|
||||
fn create_output_setups_multi(
|
||||
configs: &[ShellWindowConfig],
|
||||
global_ctx: &GlobalContext,
|
||||
connection: &Connection,
|
||||
event_queue: &mut EventQueue<AppState>,
|
||||
pointer: &Rc<WlPointer>,
|
||||
) -> Result<Vec<OutputSetup>> {
|
||||
let mut setups = Vec::new();
|
||||
|
||||
for (output_index, output) in global_ctx.outputs.iter().enumerate() {
|
||||
let is_primary = output_index == 0;
|
||||
let output_id = output.id();
|
||||
|
||||
for shell_config in configs {
|
||||
let config = &shell_config.config;
|
||||
|
||||
let mut temp_info = OutputInfo::new(OutputHandle::new());
|
||||
temp_info.set_primary(is_primary);
|
||||
|
||||
if !config.output_policy.should_render(&temp_info) {
|
||||
info!(
|
||||
"Skipping shell window '{}' on output {} due to output policy",
|
||||
shell_config.name, output_index
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let layer_surface_config = Self::create_layer_surface_config(config);
|
||||
|
||||
let setup_params = SurfaceSetupParams {
|
||||
compositor: &global_ctx.compositor,
|
||||
output,
|
||||
layer_shell: &global_ctx.layer_shell,
|
||||
fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(),
|
||||
viewporter: global_ctx.viewporter.as_ref(),
|
||||
queue_handle: &event_queue.handle(),
|
||||
layer: config.layer,
|
||||
namespace: config.namespace.clone(),
|
||||
};
|
||||
|
||||
let surface_ctx = SurfaceCtx::setup(&setup_params, &layer_surface_config);
|
||||
let main_surface_id = surface_ctx.surface.id();
|
||||
|
||||
let render_factory =
|
||||
RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager));
|
||||
|
||||
let window =
|
||||
Self::initialize_renderer(&surface_ctx.surface, config, &render_factory)?;
|
||||
|
||||
let mut builder = WindowStateBuilder::new()
|
||||
.with_component_definition(config.component_definition.clone())
|
||||
.with_compilation_result(config.compilation_result.clone())
|
||||
.with_surface(Rc::clone(&surface_ctx.surface))
|
||||
.with_layer_surface(Rc::clone(&surface_ctx.layer_surface))
|
||||
.with_scale_factor(config.scale_factor)
|
||||
.with_height(config.height)
|
||||
.with_width(config.width)
|
||||
.with_exclusive_zone(config.exclusive_zone)
|
||||
.with_connection(Rc::new(connection.clone()))
|
||||
.with_pointer(Rc::clone(pointer))
|
||||
.with_window(Rc::clone(&window));
|
||||
|
||||
if let Some(fs) = &surface_ctx.fractional_scale {
|
||||
builder = builder.with_fractional_scale(Rc::clone(fs));
|
||||
}
|
||||
|
||||
if let Some(vp) = &surface_ctx.viewport {
|
||||
builder = builder.with_viewport(Rc::clone(vp));
|
||||
}
|
||||
|
||||
info!(
|
||||
"Created setup for shell window '{}' on output {}",
|
||||
shell_config.name, output_index
|
||||
);
|
||||
|
||||
setups.push(OutputSetup {
|
||||
output_id: output_id.clone(),
|
||||
main_surface_id,
|
||||
window,
|
||||
builder,
|
||||
shell_window_name: shell_config.name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(setups)
|
||||
}
|
||||
|
||||
fn create_output_manager(params: &OutputManagerParams<'_>) -> OutputManager {
|
||||
let manager_context = OutputManagerContext {
|
||||
compositor: params.global_ctx.compositor.clone(),
|
||||
|
|
|
|||
|
|
@ -13,15 +13,31 @@ use wayland_client::backend::ObjectId;
|
|||
|
||||
pub type PerOutputWindow = WindowState;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ShellWindowKey {
|
||||
pub output_handle: OutputHandle,
|
||||
pub shell_window_name: String,
|
||||
}
|
||||
|
||||
impl ShellWindowKey {
|
||||
pub fn new(output_handle: OutputHandle, shell_window_name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
output_handle,
|
||||
shell_window_name: shell_window_name.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
output_registry: OutputRegistry,
|
||||
output_mapping: OutputMapping,
|
||||
windows: HashMap<OutputHandle, PerOutputWindow>,
|
||||
surface_to_output: HashMap<ObjectId, OutputHandle>,
|
||||
windows: HashMap<ShellWindowKey, PerOutputWindow>,
|
||||
surface_to_key: HashMap<ObjectId, ShellWindowKey>,
|
||||
_pointer: ManagedWlPointer,
|
||||
shared_pointer_serial: Rc<SharedPointerSerial>,
|
||||
output_manager: Option<Rc<RefCell<OutputManager>>>,
|
||||
registry_name_to_output_id: HashMap<u32, ObjectId>,
|
||||
active_window_key: Option<ShellWindowKey>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
|
|
@ -30,11 +46,12 @@ impl AppState {
|
|||
output_registry: OutputRegistry::new(),
|
||||
output_mapping: OutputMapping::new(),
|
||||
windows: HashMap::new(),
|
||||
surface_to_output: HashMap::new(),
|
||||
surface_to_key: HashMap::new(),
|
||||
_pointer: pointer,
|
||||
shared_pointer_serial: shared_serial,
|
||||
output_manager: None,
|
||||
registry_name_to_output_id: HashMap::new(),
|
||||
active_window_key: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,49 +75,88 @@ impl AppState {
|
|||
self.registry_name_to_output_id.remove(&name)
|
||||
}
|
||||
|
||||
pub fn add_output(
|
||||
pub fn add_shell_window(
|
||||
&mut self,
|
||||
output_id: ObjectId,
|
||||
output_id: &ObjectId,
|
||||
shell_window_name: &str,
|
||||
main_surface_id: ObjectId,
|
||||
window: PerOutputWindow,
|
||||
) {
|
||||
let handle = self.output_mapping.insert(output_id);
|
||||
self.surface_to_output.insert(main_surface_id, handle);
|
||||
let handle = self.output_mapping.get(output_id).unwrap_or_else(|| {
|
||||
let h = self.output_mapping.insert(output_id.clone());
|
||||
let is_primary = self.output_registry.is_empty();
|
||||
let mut info = OutputInfo::new(h);
|
||||
info.set_primary(is_primary);
|
||||
self.output_registry.add(info);
|
||||
h
|
||||
});
|
||||
|
||||
let is_primary = self.output_registry.is_empty();
|
||||
|
||||
let mut info = OutputInfo::new(handle);
|
||||
info.set_primary(is_primary);
|
||||
|
||||
self.output_registry.add(info);
|
||||
self.windows.insert(handle, window);
|
||||
let key = ShellWindowKey::new(handle, shell_window_name);
|
||||
self.surface_to_key.insert(main_surface_id, key.clone());
|
||||
self.windows.insert(key, window);
|
||||
}
|
||||
|
||||
pub fn remove_output(&mut self, handle: OutputHandle) -> Option<PerOutputWindow> {
|
||||
pub fn add_output(
|
||||
&mut self,
|
||||
output_id: &ObjectId,
|
||||
main_surface_id: ObjectId,
|
||||
window: PerOutputWindow,
|
||||
) {
|
||||
self.add_shell_window(output_id, "default", main_surface_id, window);
|
||||
}
|
||||
|
||||
pub fn remove_output(&mut self, handle: OutputHandle) -> Vec<PerOutputWindow> {
|
||||
self.output_registry.remove(handle);
|
||||
|
||||
let window = self.windows.remove(&handle);
|
||||
let keys_to_remove: Vec<_> = self
|
||||
.windows
|
||||
.keys()
|
||||
.filter(|k| k.output_handle == handle)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
self.surface_to_output.retain(|_, h| *h != handle);
|
||||
let mut removed = Vec::new();
|
||||
for key in keys_to_remove {
|
||||
if let Some(window) = self.windows.remove(&key) {
|
||||
removed.push(window);
|
||||
}
|
||||
}
|
||||
|
||||
window
|
||||
self.surface_to_key.retain(|_, k| k.output_handle != handle);
|
||||
|
||||
removed
|
||||
}
|
||||
|
||||
pub fn get_output_by_handle(&self, handle: OutputHandle) -> Option<&PerOutputWindow> {
|
||||
self.windows.get(&handle)
|
||||
pub fn get_window_by_key(&self, key: &ShellWindowKey) -> Option<&PerOutputWindow> {
|
||||
self.windows.get(key)
|
||||
}
|
||||
|
||||
pub fn get_output_by_handle_mut(
|
||||
pub fn get_window_by_key_mut(&mut self, key: &ShellWindowKey) -> Option<&mut PerOutputWindow> {
|
||||
self.windows.get_mut(key)
|
||||
}
|
||||
|
||||
pub fn get_window_by_name(
|
||||
&self,
|
||||
output_handle: OutputHandle,
|
||||
shell_window_name: &str,
|
||||
) -> Option<&PerOutputWindow> {
|
||||
let key = ShellWindowKey::new(output_handle, shell_window_name);
|
||||
self.windows.get(&key)
|
||||
}
|
||||
|
||||
pub fn get_window_by_name_mut(
|
||||
&mut self,
|
||||
handle: OutputHandle,
|
||||
output_handle: OutputHandle,
|
||||
shell_window_name: &str,
|
||||
) -> Option<&mut PerOutputWindow> {
|
||||
self.windows.get_mut(&handle)
|
||||
let key = ShellWindowKey::new(output_handle, shell_window_name);
|
||||
self.windows.get_mut(&key)
|
||||
}
|
||||
|
||||
pub fn get_output_by_output_id(&self, output_id: &ObjectId) -> Option<&PerOutputWindow> {
|
||||
self.output_mapping
|
||||
.get(output_id)
|
||||
.and_then(|handle| self.windows.get(&handle))
|
||||
.and_then(|handle| self.get_first_window_for_output(handle))
|
||||
}
|
||||
|
||||
pub fn get_output_by_output_id_mut(
|
||||
|
|
@ -109,22 +165,39 @@ impl AppState {
|
|||
) -> Option<&mut PerOutputWindow> {
|
||||
self.output_mapping
|
||||
.get(output_id)
|
||||
.and_then(|handle| self.windows.get_mut(&handle))
|
||||
.and_then(|handle| self.get_first_window_for_output_mut(handle))
|
||||
}
|
||||
|
||||
fn get_first_window_for_output(&self, handle: OutputHandle) -> Option<&PerOutputWindow> {
|
||||
self.windows
|
||||
.iter()
|
||||
.find(|(k, _)| k.output_handle == handle)
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
|
||||
fn get_first_window_for_output_mut(
|
||||
&mut self,
|
||||
handle: OutputHandle,
|
||||
) -> Option<&mut PerOutputWindow> {
|
||||
self.windows
|
||||
.iter_mut()
|
||||
.find(|(k, _)| k.output_handle == handle)
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
|
||||
pub fn get_output_by_surface(&self, surface_id: &ObjectId) -> Option<&PerOutputWindow> {
|
||||
self.surface_to_output
|
||||
self.surface_to_key
|
||||
.get(surface_id)
|
||||
.and_then(|handle| self.windows.get(handle))
|
||||
.and_then(|key| self.windows.get(key))
|
||||
}
|
||||
|
||||
pub fn get_output_by_surface_mut(
|
||||
&mut self,
|
||||
surface_id: &ObjectId,
|
||||
) -> Option<&mut PerOutputWindow> {
|
||||
self.surface_to_output
|
||||
self.surface_to_key
|
||||
.get(surface_id)
|
||||
.and_then(|handle| self.windows.get_mut(handle))
|
||||
.and_then(|key| self.windows.get_mut(key))
|
||||
}
|
||||
|
||||
pub fn get_output_by_layer_surface_mut(
|
||||
|
|
@ -136,8 +209,14 @@ impl AppState {
|
|||
.find(|window| window.layer_surface().as_ref().id() == *layer_surface_id)
|
||||
}
|
||||
|
||||
pub fn get_key_by_surface(&self, surface_id: &ObjectId) -> Option<&ShellWindowKey> {
|
||||
self.surface_to_key.get(surface_id)
|
||||
}
|
||||
|
||||
pub fn get_handle_by_surface(&self, surface_id: &ObjectId) -> Option<OutputHandle> {
|
||||
self.surface_to_output.get(surface_id).copied()
|
||||
self.surface_to_key
|
||||
.get(surface_id)
|
||||
.map(|key| key.output_handle)
|
||||
}
|
||||
|
||||
pub fn get_handle_by_output_id(&self, output_id: &ObjectId) -> Option<OutputHandle> {
|
||||
|
|
@ -152,10 +231,28 @@ impl AppState {
|
|||
self.output_registry.active_handle()
|
||||
}
|
||||
|
||||
pub fn set_active_window_key(&mut self, key: Option<ShellWindowKey>) {
|
||||
if let Some(ref k) = key {
|
||||
self.output_registry.set_active(Some(k.output_handle));
|
||||
} else {
|
||||
self.output_registry.set_active(None);
|
||||
}
|
||||
self.active_window_key = key;
|
||||
}
|
||||
|
||||
pub fn active_window_key(&self) -> Option<&ShellWindowKey> {
|
||||
self.active_window_key.as_ref()
|
||||
}
|
||||
|
||||
pub fn active_window_mut(&mut self) -> Option<&mut PerOutputWindow> {
|
||||
let key = self.active_window_key.clone()?;
|
||||
self.windows.get_mut(&key)
|
||||
}
|
||||
|
||||
pub fn primary_output(&self) -> Option<&PerOutputWindow> {
|
||||
self.output_registry
|
||||
.primary_handle()
|
||||
.and_then(|handle| self.windows.get(&handle))
|
||||
.and_then(|handle| self.get_first_window_for_output(handle))
|
||||
}
|
||||
|
||||
pub fn primary_output_handle(&self) -> Option<OutputHandle> {
|
||||
|
|
@ -165,7 +262,7 @@ impl AppState {
|
|||
pub fn active_output(&self) -> Option<&PerOutputWindow> {
|
||||
self.output_registry
|
||||
.active_handle()
|
||||
.and_then(|handle| self.windows.get(&handle))
|
||||
.and_then(|handle| self.get_first_window_for_output(handle))
|
||||
}
|
||||
|
||||
pub fn all_outputs(&self) -> impl Iterator<Item = &PerOutputWindow> {
|
||||
|
|
@ -176,10 +273,18 @@ impl AppState {
|
|||
self.windows.values_mut()
|
||||
}
|
||||
|
||||
pub fn outputs_with_handles(&self) -> impl Iterator<Item = (OutputHandle, &PerOutputWindow)> {
|
||||
pub fn windows_for_output(
|
||||
&self,
|
||||
handle: OutputHandle,
|
||||
) -> impl Iterator<Item = (&str, &PerOutputWindow)> {
|
||||
self.windows
|
||||
.iter()
|
||||
.map(|(&handle, window)| (handle, window))
|
||||
.filter(move |(k, _)| k.output_handle == handle)
|
||||
.map(|(k, v)| (k.shell_window_name.as_str(), v))
|
||||
}
|
||||
|
||||
pub fn windows_with_keys(&self) -> impl Iterator<Item = (&ShellWindowKey, &PerOutputWindow)> {
|
||||
self.windows.iter()
|
||||
}
|
||||
|
||||
pub const fn shared_pointer_serial(&self) -> &Rc<SharedPointerSerial> {
|
||||
|
|
@ -209,16 +314,28 @@ impl AppState {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_handle_by_popup(&self, popup_surface_id: &ObjectId) -> Option<OutputHandle> {
|
||||
self.windows.iter().find_map(|(&handle, window)| {
|
||||
pub fn get_key_by_popup(&self, popup_surface_id: &ObjectId) -> Option<&ShellWindowKey> {
|
||||
self.windows.iter().find_map(|(key, window)| {
|
||||
window
|
||||
.popup_manager()
|
||||
.as_ref()
|
||||
.and_then(|pm| pm.find_by_surface(popup_surface_id))
|
||||
.map(|_| handle)
|
||||
.map(|_| key)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_handle_by_popup(&self, popup_surface_id: &ObjectId) -> Option<OutputHandle> {
|
||||
self.get_key_by_popup(popup_surface_id)
|
||||
.map(|key| key.output_handle)
|
||||
}
|
||||
|
||||
pub fn get_output_by_handle_mut(
|
||||
&mut self,
|
||||
handle: OutputHandle,
|
||||
) -> Option<&mut PerOutputWindow> {
|
||||
self.get_first_window_for_output_mut(handle)
|
||||
}
|
||||
|
||||
pub fn get_output_info(&self, handle: OutputHandle) -> Option<&OutputInfo> {
|
||||
self.output_registry.get(handle)
|
||||
}
|
||||
|
|
@ -231,13 +348,61 @@ impl AppState {
|
|||
self.output_registry.all_info()
|
||||
}
|
||||
|
||||
pub fn outputs_with_info(&self) -> impl Iterator<Item = (&OutputInfo, &PerOutputWindow)> {
|
||||
self.output_registry
|
||||
.all()
|
||||
.filter_map(|(handle, info)| self.windows.get(&handle).map(|window| (info, window)))
|
||||
}
|
||||
|
||||
pub const fn output_registry(&self) -> &OutputRegistry {
|
||||
&self.output_registry
|
||||
}
|
||||
|
||||
pub fn shell_window_names(&self) -> Vec<&str> {
|
||||
let mut names: Vec<_> = self
|
||||
.windows
|
||||
.keys()
|
||||
.map(|k| k.shell_window_name.as_str())
|
||||
.collect();
|
||||
names.sort_unstable();
|
||||
names.dedup();
|
||||
names
|
||||
}
|
||||
|
||||
pub fn windows_by_shell_name(
|
||||
&self,
|
||||
shell_window_name: &str,
|
||||
) -> impl Iterator<Item = &PerOutputWindow> {
|
||||
self.windows
|
||||
.iter()
|
||||
.filter(move |(k, _)| k.shell_window_name == shell_window_name)
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
|
||||
pub fn get_output_by_handle(&self, handle: OutputHandle) -> Option<&PerOutputWindow> {
|
||||
self.get_first_window_for_output(handle)
|
||||
}
|
||||
|
||||
pub fn outputs_with_handles(&self) -> impl Iterator<Item = (OutputHandle, &PerOutputWindow)> {
|
||||
self.windows
|
||||
.iter()
|
||||
.map(|(key, window)| (key.output_handle, window))
|
||||
}
|
||||
|
||||
pub fn outputs_with_info(&self) -> impl Iterator<Item = (&OutputInfo, &PerOutputWindow)> {
|
||||
self.output_registry.all_info().filter_map(|info| {
|
||||
let handle = info.handle();
|
||||
self.get_first_window_for_output(handle)
|
||||
.map(|window| (info, window))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all_windows_for_output_mut(
|
||||
&mut self,
|
||||
output_id: &ObjectId,
|
||||
) -> Vec<&mut PerOutputWindow> {
|
||||
let Some(handle) = self.output_mapping.get(output_id) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
self.windows
|
||||
.iter_mut()
|
||||
.filter(|(k, _)| k.output_handle == handle)
|
||||
.map(|(_, v)| v)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,8 @@ impl SurfaceCtx {
|
|||
|
||||
layer_surface.set_exclusive_zone(config.exclusive_zone);
|
||||
layer_surface.set_keyboard_interactivity(config.keyboard_interactivity);
|
||||
layer_surface.set_size(1, config.height);
|
||||
|
||||
layer_surface.set_size(config.width, config.height);
|
||||
surface.commit();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,4 +49,8 @@ impl<W: RenderableWindow> RenderingState<W> {
|
|||
pub fn fractional_scale(&self) -> Option<&ManagedWpFractionalScaleV1> {
|
||||
self.renderer.fractional_scale()
|
||||
}
|
||||
|
||||
pub fn commit_surface(&self) {
|
||||
self.renderer.commit_surface();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ pub struct WindowStateBuilder {
|
|||
pub connection: Option<Rc<Connection>>,
|
||||
pub scale_factor: f32,
|
||||
pub height: u32,
|
||||
pub width: u32,
|
||||
pub exclusive_zone: i32,
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +101,12 @@ impl WindowStateBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn with_width(mut self, width: u32) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_component_definition(mut self, component_definition: ComponentDefinition) -> Self {
|
||||
self.component_definition = Some(component_definition);
|
||||
|
|
@ -163,6 +170,7 @@ impl Default for WindowStateBuilder {
|
|||
connection: None,
|
||||
scale_factor: 1.0,
|
||||
height: 30,
|
||||
width: 0,
|
||||
exclusive_zone: -1,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ impl WindowState {
|
|||
viewport,
|
||||
fractional_scale,
|
||||
height: builder.height,
|
||||
exclusive_zone: builder.exclusive_zone,
|
||||
size,
|
||||
});
|
||||
|
||||
|
|
@ -143,10 +142,14 @@ impl WindowState {
|
|||
Rc::clone(self.rendering.window())
|
||||
}
|
||||
|
||||
pub(crate) fn layer_surface(&self) -> Rc<ZwlrLayerSurfaceV1> {
|
||||
pub fn layer_surface(&self) -> Rc<ZwlrLayerSurfaceV1> {
|
||||
self.rendering.layer_surface()
|
||||
}
|
||||
|
||||
pub fn commit_surface(&self) {
|
||||
self.rendering.commit_surface();
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u32 {
|
||||
self.rendering.height()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ pub struct WindowRendererParams<W: RenderableWindow> {
|
|||
pub viewport: Option<ManagedWpViewport>,
|
||||
pub fractional_scale: Option<ManagedWpFractionalScaleV1>,
|
||||
pub height: u32,
|
||||
pub exclusive_zone: i32,
|
||||
pub size: PhysicalSize,
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +34,6 @@ pub struct WindowRenderer<W: RenderableWindow> {
|
|||
viewport: Option<ManagedWpViewport>,
|
||||
fractional_scale: Option<ManagedWpFractionalScaleV1>,
|
||||
height: u32,
|
||||
exclusive_zone: i32,
|
||||
size: PhysicalSize,
|
||||
logical_size: PhysicalSize,
|
||||
}
|
||||
|
|
@ -50,7 +48,6 @@ impl<W: RenderableWindow> WindowRenderer<W> {
|
|||
viewport: params.viewport,
|
||||
fractional_scale: params.fractional_scale,
|
||||
height: params.height,
|
||||
exclusive_zone: params.exclusive_zone,
|
||||
size: params.size,
|
||||
logical_size: PhysicalSize::default(),
|
||||
}
|
||||
|
|
@ -137,9 +134,6 @@ impl<W: RenderableWindow> WindowRenderer<W> {
|
|||
}
|
||||
}
|
||||
|
||||
self.layer_surface
|
||||
.set_size(dimensions.logical_width(), dimensions.logical_height());
|
||||
self.layer_surface.set_exclusive_zone(self.exclusive_zone);
|
||||
self.surface.commit();
|
||||
}
|
||||
|
||||
|
|
@ -191,4 +185,8 @@ impl<W: RenderableWindow> WindowRenderer<W> {
|
|||
pub const fn fractional_scale(&self) -> Option<&ManagedWpFractionalScaleV1> {
|
||||
self.fractional_scale.as_ref()
|
||||
}
|
||||
|
||||
pub fn commit_surface(&self) {
|
||||
self.surface.commit();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use layer_shika_adapters::platform::slint_interpreter::{CompilationResult, Compi
|
|||
use layer_shika_domain::errors::DomainError;
|
||||
use layer_shika_domain::prelude::{
|
||||
AnchorEdges, KeyboardInteractivity, Layer, Margins, OutputPolicy, ScaleFactor, WindowConfig,
|
||||
WindowHeight,
|
||||
WindowDimension,
|
||||
};
|
||||
use spin_on::spin_on;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -136,9 +136,21 @@ impl LayerShika<NeedsComponent> {
|
|||
}
|
||||
|
||||
impl LayerShika<HasComponent> {
|
||||
#[must_use]
|
||||
pub fn dimensions(mut self, width: u32, height: u32) -> Self {
|
||||
self.config.dimensions = WindowDimension::new(width, height);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn height(mut self, height: u32) -> Self {
|
||||
self.config.height = WindowHeight::new(height);
|
||||
self.config.dimensions = WindowDimension::new(self.config.dimensions.width(), height);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn width(mut self, width: u32) -> Self {
|
||||
self.config.dimensions = WindowDimension::new(width, self.config.dimensions.height());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
mod builder;
|
||||
mod popup_builder;
|
||||
mod shell;
|
||||
mod shell_composition;
|
||||
mod system;
|
||||
mod value_conversion;
|
||||
|
||||
|
|
@ -27,6 +29,12 @@ pub use layer_shika_domain::value_objects::popup_request::{
|
|||
pub use popup_builder::PopupBuilder;
|
||||
pub use system::{App, EventContext, EventLoopHandle, ShellControl};
|
||||
|
||||
pub use shell::{
|
||||
LayerSurfaceHandle, Shell, ShellEventContext, ShellEventLoopHandle, ShellWindowConfigHandler,
|
||||
ShellWindowHandle,
|
||||
};
|
||||
pub use shell_composition::{ShellComposition, ShellWindowDefinition};
|
||||
|
||||
pub mod calloop {
|
||||
pub use layer_shika_adapters::platform::calloop::{
|
||||
Generic, Interest, Mode, PostAction, RegistrationToken, TimeoutAction, Timer, channel,
|
||||
|
|
@ -55,7 +63,16 @@ pub mod prelude {
|
|||
PopupWindow, Result, ShellControl,
|
||||
};
|
||||
|
||||
pub use crate::{
|
||||
LayerSurfaceHandle, Shell, ShellComposition, ShellEventContext, ShellEventLoopHandle,
|
||||
ShellWindowConfigHandler, ShellWindowDefinition, ShellWindowHandle,
|
||||
};
|
||||
|
||||
pub use crate::calloop::{Generic, Interest, Mode, PostAction, RegistrationToken, Timer};
|
||||
|
||||
pub use crate::{slint, slint_interpreter};
|
||||
|
||||
pub use layer_shika_domain::prelude::{Margins, ScaleFactor, WindowConfig, WindowDimension};
|
||||
|
||||
pub use layer_shika_adapters::platform::wayland::Anchor;
|
||||
}
|
||||
|
|
|
|||
449
crates/composition/src/shell.rs
Normal file
449
crates/composition/src/shell.rs
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
use crate::shell_composition::ShellWindowDefinition;
|
||||
use crate::system::{EventContext, PopupCommand, ShellControl};
|
||||
use crate::{Error, Result};
|
||||
use layer_shika_adapters::errors::EventLoopError;
|
||||
use layer_shika_adapters::platform::calloop::{
|
||||
EventSource, Generic, Interest, Mode, PostAction, RegistrationToken, TimeoutAction, Timer,
|
||||
channel,
|
||||
};
|
||||
use layer_shika_adapters::platform::slint_interpreter::{CompilationResult, ComponentInstance};
|
||||
use layer_shika_adapters::platform::wayland::Anchor;
|
||||
use layer_shika_adapters::{
|
||||
AppState, ShellWindowConfig, WaylandWindowConfig, WindowState, WindowingSystemFacade,
|
||||
};
|
||||
use layer_shika_domain::config::WindowConfig;
|
||||
use layer_shika_domain::errors::DomainError;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::os::unix::io::AsFd;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::result::Result as StdResult;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct LayerSurfaceHandle<'a> {
|
||||
window_state: &'a WindowState,
|
||||
}
|
||||
|
||||
impl LayerSurfaceHandle<'_> {
|
||||
pub fn set_anchor(&self, anchor: Anchor) {
|
||||
self.window_state.layer_surface().set_anchor(anchor);
|
||||
}
|
||||
|
||||
pub fn set_size(&self, width: u32, height: u32) {
|
||||
self.window_state.layer_surface().set_size(width, height);
|
||||
}
|
||||
|
||||
pub fn set_exclusive_zone(&self, zone: i32) {
|
||||
self.window_state.layer_surface().set_exclusive_zone(zone);
|
||||
}
|
||||
|
||||
pub fn commit(&self) {
|
||||
self.window_state.commit_surface();
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ShellWindowConfigHandler {
|
||||
fn configure_window(&self, instance: &ComponentInstance, surface: LayerSurfaceHandle<'_>);
|
||||
}
|
||||
|
||||
impl<F> ShellWindowConfigHandler for F
|
||||
where
|
||||
F: Fn(&ComponentInstance, LayerSurfaceHandle<'_>),
|
||||
{
|
||||
fn configure_window(&self, instance: &ComponentInstance, surface: LayerSurfaceHandle<'_>) {
|
||||
self(instance, surface);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShellWindowHandle {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct Shell {
|
||||
inner: Rc<RefCell<WindowingSystemFacade>>,
|
||||
windows: HashMap<String, ShellWindowHandle>,
|
||||
compilation_result: Rc<CompilationResult>,
|
||||
popup_command_sender: channel::Sender<PopupCommand>,
|
||||
}
|
||||
|
||||
impl Shell {
|
||||
pub(crate) fn new(
|
||||
compilation_result: Rc<CompilationResult>,
|
||||
definitions: &[ShellWindowDefinition],
|
||||
) -> Result<Self> {
|
||||
log::info!("Creating shell with {} windows", definitions.len());
|
||||
|
||||
if definitions.is_empty() {
|
||||
return Err(Error::Domain(DomainError::Configuration {
|
||||
message: "At least one shell window definition is required".to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
let shell_configs: Vec<ShellWindowConfig> = definitions
|
||||
.iter()
|
||||
.map(|def| {
|
||||
let component_definition = compilation_result
|
||||
.component(&def.component_name)
|
||||
.ok_or_else(|| {
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: format!(
|
||||
"Component '{}' not found in compilation result",
|
||||
def.component_name
|
||||
),
|
||||
})
|
||||
})?;
|
||||
|
||||
let wayland_config = WaylandWindowConfig::from_domain_config(
|
||||
component_definition,
|
||||
Some(Rc::clone(&compilation_result)),
|
||||
def.config.clone(),
|
||||
);
|
||||
|
||||
Ok(ShellWindowConfig {
|
||||
name: def.component_name.clone(),
|
||||
config: wayland_config,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let inner = layer_shika_adapters::WaylandWindowingSystem::new_multi(&shell_configs)?;
|
||||
let facade = WindowingSystemFacade::new(inner);
|
||||
let inner_rc = Rc::new(RefCell::new(facade));
|
||||
|
||||
let (sender, receiver) = channel::channel();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
for def in definitions {
|
||||
windows.insert(
|
||||
def.component_name.clone(),
|
||||
ShellWindowHandle {
|
||||
name: def.component_name.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let shell = Self {
|
||||
inner: Rc::clone(&inner_rc),
|
||||
windows,
|
||||
compilation_result,
|
||||
popup_command_sender: sender,
|
||||
};
|
||||
|
||||
shell.setup_popup_command_handler(receiver)?;
|
||||
|
||||
log::info!(
|
||||
"Shell created with windows: {:?}",
|
||||
shell.shell_window_names()
|
||||
);
|
||||
|
||||
Ok(shell)
|
||||
}
|
||||
|
||||
pub(crate) fn new_auto_discover(
|
||||
compilation_result: Rc<CompilationResult>,
|
||||
component_names: &[String],
|
||||
) -> Result<Self> {
|
||||
log::info!(
|
||||
"Creating shell with auto-discovery for {} components",
|
||||
component_names.len()
|
||||
);
|
||||
|
||||
if component_names.is_empty() {
|
||||
return Err(Error::Domain(DomainError::Configuration {
|
||||
message: "At least one component name is required for auto-discovery".to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
let default_config = WindowConfig::default();
|
||||
|
||||
let shell_configs: Vec<ShellWindowConfig> = component_names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let component_definition = compilation_result.component(name).ok_or_else(|| {
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: format!("Component '{}' not found in compilation result", name),
|
||||
})
|
||||
})?;
|
||||
|
||||
let wayland_config = WaylandWindowConfig::from_domain_config(
|
||||
component_definition,
|
||||
Some(Rc::clone(&compilation_result)),
|
||||
default_config.clone(),
|
||||
);
|
||||
|
||||
Ok(ShellWindowConfig {
|
||||
name: name.clone(),
|
||||
config: wayland_config,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let inner = layer_shika_adapters::WaylandWindowingSystem::new_multi(&shell_configs)?;
|
||||
let facade = WindowingSystemFacade::new(inner);
|
||||
let inner_rc = Rc::new(RefCell::new(facade));
|
||||
|
||||
let (sender, receiver) = channel::channel();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
for name in component_names {
|
||||
windows.insert(name.clone(), ShellWindowHandle { name: name.clone() });
|
||||
}
|
||||
|
||||
let shell = Self {
|
||||
inner: Rc::clone(&inner_rc),
|
||||
windows,
|
||||
compilation_result,
|
||||
popup_command_sender: sender,
|
||||
};
|
||||
|
||||
shell.setup_popup_command_handler(receiver)?;
|
||||
|
||||
log::info!(
|
||||
"Shell created with auto-discovered windows: {:?}",
|
||||
shell.shell_window_names()
|
||||
);
|
||||
|
||||
Ok(shell)
|
||||
}
|
||||
|
||||
pub fn apply_window_config<H: ShellWindowConfigHandler>(&self, handler: &H) {
|
||||
log::info!("Applying window configuration via handler");
|
||||
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
for window in system.app_state().all_outputs() {
|
||||
let instance = window.component_instance();
|
||||
let surface_handle = LayerSurfaceHandle {
|
||||
window_state: window,
|
||||
};
|
||||
handler.configure_window(instance, surface_handle);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_window_config_fn<F>(&self, f: F)
|
||||
where
|
||||
F: Fn(&ComponentInstance, LayerSurfaceHandle<'_>),
|
||||
{
|
||||
self.apply_window_config(&f);
|
||||
}
|
||||
|
||||
fn setup_popup_command_handler(&self, receiver: channel::Channel<PopupCommand>) -> Result<()> {
|
||||
let loop_handle = self.inner.borrow().inner_ref().event_loop_handle();
|
||||
let control = self.control();
|
||||
|
||||
loop_handle
|
||||
.insert_source(receiver, move |event, (), app_state| {
|
||||
if let channel::Event::Msg(command) = event {
|
||||
let mut ctx = EventContext::from_app_state(app_state);
|
||||
|
||||
match command {
|
||||
PopupCommand::Show(request) => {
|
||||
if let Err(e) = ctx.show_popup(&request, Some(control.clone())) {
|
||||
log::error!("Failed to show popup: {}", e);
|
||||
}
|
||||
}
|
||||
PopupCommand::Close(handle) => {
|
||||
if let Err(e) = ctx.close_popup(handle) {
|
||||
log::error!("Failed to close popup: {}", e);
|
||||
}
|
||||
}
|
||||
PopupCommand::Resize {
|
||||
handle,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
if let Err(e) = ctx.resize_popup(handle, width, height) {
|
||||
log::error!("Failed to resize popup: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(|e| {
|
||||
Error::Adapter(
|
||||
EventLoopError::InsertSource {
|
||||
message: format!("Failed to setup popup command handler: {e:?}"),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn control(&self) -> ShellControl {
|
||||
ShellControl::new(self.popup_command_sender.clone())
|
||||
}
|
||||
|
||||
pub fn shell_window(&self, name: &str) -> Option<&ShellWindowHandle> {
|
||||
self.windows.get(name)
|
||||
}
|
||||
|
||||
pub fn shell_window_names(&self) -> Vec<&str> {
|
||||
self.windows.keys().map(String::as_str).collect()
|
||||
}
|
||||
|
||||
pub fn event_loop_handle(&self) -> ShellEventLoopHandle {
|
||||
ShellEventLoopHandle {
|
||||
system: Rc::downgrade(&self.inner),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
log::info!(
|
||||
"Starting shell event loop with {} windows",
|
||||
self.windows.len()
|
||||
);
|
||||
self.inner.borrow_mut().run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn with_component<F>(&self, shell_window_name: &str, mut f: F)
|
||||
where
|
||||
F: FnMut(&ComponentInstance),
|
||||
{
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
if self.windows.contains_key(shell_window_name) {
|
||||
for window in system.app_state().windows_by_shell_name(shell_window_name) {
|
||||
f(window.component_instance());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_all_components<F>(&self, mut f: F)
|
||||
where
|
||||
F: FnMut(&str, &ComponentInstance),
|
||||
{
|
||||
let facade = self.inner.borrow();
|
||||
let system = facade.inner_ref();
|
||||
|
||||
for name in self.windows.keys() {
|
||||
if let Some(window) = system.app_state().primary_output() {
|
||||
f(name, window.component_instance());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compilation_result(&self) -> &Rc<CompilationResult> {
|
||||
&self.compilation_result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShellEventLoopHandle {
|
||||
system: Weak<RefCell<WindowingSystemFacade>>,
|
||||
}
|
||||
|
||||
impl ShellEventLoopHandle {
|
||||
pub fn insert_source<S, F, R>(
|
||||
&self,
|
||||
source: S,
|
||||
mut callback: F,
|
||||
) -> StdResult<RegistrationToken, Error>
|
||||
where
|
||||
S: EventSource<Ret = R> + 'static,
|
||||
F: FnMut(S::Event, &mut S::Metadata, ShellEventContext<'_>) -> R + 'static,
|
||||
{
|
||||
let system = self.system.upgrade().ok_or(Error::SystemDropped)?;
|
||||
let loop_handle = system.borrow().inner_ref().event_loop_handle();
|
||||
|
||||
loop_handle
|
||||
.insert_source(source, move |event, metadata, app_state| {
|
||||
let ctx = ShellEventContext { app_state };
|
||||
callback(event, metadata, ctx)
|
||||
})
|
||||
.map_err(|e| {
|
||||
Error::Adapter(
|
||||
EventLoopError::InsertSource {
|
||||
message: format!("{e:?}"),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_timer<F>(&self, duration: Duration, mut callback: F) -> Result<RegistrationToken>
|
||||
where
|
||||
F: FnMut(Instant, ShellEventContext<'_>) -> TimeoutAction + 'static,
|
||||
{
|
||||
let timer = Timer::from_duration(duration);
|
||||
self.insert_source(timer, move |deadline, (), ctx| callback(deadline, ctx))
|
||||
}
|
||||
|
||||
pub fn add_channel<T, F>(
|
||||
&self,
|
||||
mut callback: F,
|
||||
) -> Result<(RegistrationToken, channel::Sender<T>)>
|
||||
where
|
||||
T: 'static,
|
||||
F: FnMut(T, ShellEventContext<'_>) + 'static,
|
||||
{
|
||||
let (sender, receiver) = channel::channel();
|
||||
let token = self.insert_source(receiver, move |event, (), ctx| {
|
||||
if let channel::Event::Msg(msg) = event {
|
||||
callback(msg, ctx);
|
||||
}
|
||||
})?;
|
||||
Ok((token, sender))
|
||||
}
|
||||
|
||||
pub fn add_fd<F, T>(
|
||||
&self,
|
||||
fd: T,
|
||||
interest: Interest,
|
||||
mode: Mode,
|
||||
mut callback: F,
|
||||
) -> Result<RegistrationToken>
|
||||
where
|
||||
T: AsFd + 'static,
|
||||
F: FnMut(ShellEventContext<'_>) + 'static,
|
||||
{
|
||||
let generic = Generic::new(fd, interest, mode);
|
||||
self.insert_source(generic, move |_readiness, _fd, ctx| {
|
||||
callback(ctx);
|
||||
Ok(PostAction::Continue)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShellEventContext<'a> {
|
||||
app_state: &'a mut AppState,
|
||||
}
|
||||
|
||||
impl ShellEventContext<'_> {
|
||||
pub fn get_shell_window_component(
|
||||
&self,
|
||||
_shell_window_name: &str,
|
||||
) -> Option<&ComponentInstance> {
|
||||
self.app_state
|
||||
.primary_output()
|
||||
.map(WindowState::component_instance)
|
||||
}
|
||||
|
||||
pub fn get_shell_window_component_mut(
|
||||
&mut self,
|
||||
_shell_window_name: &str,
|
||||
) -> Option<&ComponentInstance> {
|
||||
self.app_state
|
||||
.primary_output()
|
||||
.map(WindowState::component_instance)
|
||||
}
|
||||
|
||||
pub fn all_shell_window_components(&self) -> impl Iterator<Item = &ComponentInstance> {
|
||||
self.app_state
|
||||
.all_outputs()
|
||||
.map(WindowState::component_instance)
|
||||
}
|
||||
|
||||
pub fn render_frame_if_dirty(&mut self) -> Result<()> {
|
||||
for window in self.app_state.all_outputs() {
|
||||
window.render_frame_if_dirty()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
83
crates/composition/src/shell_composition.rs
Normal file
83
crates/composition/src/shell_composition.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use crate::shell::Shell;
|
||||
use crate::{Error, Result};
|
||||
use layer_shika_adapters::platform::slint_interpreter::CompilationResult;
|
||||
use layer_shika_domain::config::WindowConfig;
|
||||
use layer_shika_domain::errors::DomainError;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShellWindowDefinition {
|
||||
pub component_name: String,
|
||||
pub config: WindowConfig,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct ShellComposition {
|
||||
compilation_result: Option<Rc<CompilationResult>>,
|
||||
shell_windows: Vec<ShellWindowDefinition>,
|
||||
auto_discover_components: Vec<String>,
|
||||
}
|
||||
|
||||
impl ShellComposition {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
compilation_result: None,
|
||||
shell_windows: Vec::new(),
|
||||
auto_discover_components: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_compilation_result(mut self, result: Rc<CompilationResult>) -> Self {
|
||||
self.compilation_result = Some(result);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn register_shell_window(
|
||||
mut self,
|
||||
component_name: impl Into<String>,
|
||||
config: WindowConfig,
|
||||
) -> Self {
|
||||
self.shell_windows.push(ShellWindowDefinition {
|
||||
component_name: component_name.into(),
|
||||
config,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn register_shell_windows(mut self, definitions: Vec<ShellWindowDefinition>) -> Self {
|
||||
self.shell_windows.extend(definitions);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn auto_discover(mut self, component_names: Vec<impl Into<String>>) -> Self {
|
||||
self.auto_discover_components = component_names.into_iter().map(Into::into).collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Shell> {
|
||||
let compilation_result = self.compilation_result.ok_or_else(|| {
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: "No compilation result provided. Use with_compilation_result()"
|
||||
.to_string(),
|
||||
})
|
||||
})?;
|
||||
|
||||
if !self.auto_discover_components.is_empty() {
|
||||
return Shell::new_auto_discover(compilation_result, &self.auto_discover_components);
|
||||
}
|
||||
|
||||
if self.shell_windows.is_empty() {
|
||||
return Err(Error::Domain(DomainError::Configuration {
|
||||
message: "No shell windows registered. Use register_shell_window(), register_shell_windows(), or auto_discover()".to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
Shell::new(compilation_result, &self.shell_windows)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ShellComposition {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +46,10 @@ pub struct ShellControl {
|
|||
}
|
||||
|
||||
impl ShellControl {
|
||||
pub fn new(sender: channel::Sender<PopupCommand>) -> Self {
|
||||
Self { sender }
|
||||
}
|
||||
|
||||
pub fn show_popup(&self, request: &PopupRequest) -> Result<()> {
|
||||
self.sender
|
||||
.send(PopupCommand::Show(request.clone()))
|
||||
|
|
@ -198,6 +202,12 @@ pub struct EventContext<'a> {
|
|||
app_state: &'a mut AppState,
|
||||
}
|
||||
|
||||
impl<'a> EventContext<'a> {
|
||||
pub fn from_app_state(app_state: &'a mut AppState) -> Self {
|
||||
Self { app_state }
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_dimensions_from_callback(args: &[Value]) -> PopupDimensions {
|
||||
let defaults = PopupDimensions::default();
|
||||
PopupDimensions::new(
|
||||
|
|
@ -289,15 +299,21 @@ impl EventContext<'_> {
|
|||
req: &PopupRequest,
|
||||
resize_control: Option<ShellControl>,
|
||||
) -> Result<PopupHandle> {
|
||||
log::info!("show_popup called for component '{}'", req.component);
|
||||
|
||||
let compilation_result = self.compilation_result().ok_or_else(|| {
|
||||
log::error!("No compilation result available");
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: "No compilation result available for popup creation".to_string(),
|
||||
})
|
||||
})?;
|
||||
|
||||
log::debug!("Got compilation result, looking for component '{}'", req.component);
|
||||
|
||||
let definition = compilation_result
|
||||
.component(&req.component)
|
||||
.ok_or_else(|| {
|
||||
log::error!("Component '{}' not found in compilation result", req.component);
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: format!(
|
||||
"{} component not found in compilation result",
|
||||
|
|
@ -306,10 +322,13 @@ impl EventContext<'_> {
|
|||
})
|
||||
})?;
|
||||
|
||||
log::debug!("Found component definition for '{}'", req.component);
|
||||
|
||||
self.close_current_popup()?;
|
||||
|
||||
let is_using_active = self.app_state.active_output().is_some();
|
||||
let active_window = self.active_or_primary_output().ok_or_else(|| {
|
||||
log::error!("No active or primary output available");
|
||||
Error::Domain(DomainError::Configuration {
|
||||
message: "No active or primary output available".to_string(),
|
||||
})
|
||||
|
|
@ -356,10 +375,6 @@ impl EventContext<'_> {
|
|||
popup_key_cell.set(popup_handle.key());
|
||||
|
||||
if let Some(popup_window) = popup_manager.get_popup_window(popup_handle.key()) {
|
||||
if matches!(req.size, PopupSize::Content) {
|
||||
log::debug!("Marking content-sized popup as repositioning from creation");
|
||||
popup_window.begin_repositioning();
|
||||
}
|
||||
popup_window.set_component_instance(instance);
|
||||
} else {
|
||||
return Err(Error::Domain(DomainError::Configuration {
|
||||
|
|
@ -425,6 +440,7 @@ impl EventContext<'_> {
|
|||
let logical_height = height as i32;
|
||||
|
||||
popup_manager.update_popup_viewport(handle.key(), logical_width, logical_height);
|
||||
popup_manager.commit_popup_surface(handle.key());
|
||||
log::debug!(
|
||||
"Updated popup viewport to logical size: {}x{} (from resize to {}x{})",
|
||||
logical_width,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::dimensions::ScaleFactor;
|
||||
use crate::value_objects::anchor::AnchorEdges;
|
||||
use crate::value_objects::dimensions::WindowHeight;
|
||||
use crate::value_objects::dimensions::WindowDimension;
|
||||
use crate::value_objects::keyboard_interactivity::KeyboardInteractivity;
|
||||
use crate::value_objects::layer::Layer;
|
||||
use crate::value_objects::margins::Margins;
|
||||
|
|
@ -8,7 +8,7 @@ use crate::value_objects::output_policy::OutputPolicy;
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WindowConfig {
|
||||
pub height: WindowHeight,
|
||||
pub dimensions: WindowDimension,
|
||||
pub margin: Margins,
|
||||
pub exclusive_zone: i32,
|
||||
pub scale_factor: ScaleFactor,
|
||||
|
|
@ -23,7 +23,7 @@ impl WindowConfig {
|
|||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
height: WindowHeight::default(),
|
||||
dimensions: WindowDimension::default(),
|
||||
margin: Margins::default(),
|
||||
exclusive_zone: -1,
|
||||
namespace: "layer-shika".to_owned(),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ 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::dimensions::{PopupDimensions, WindowDimension};
|
||||
pub use crate::value_objects::keyboard_interactivity::KeyboardInteractivity;
|
||||
pub use crate::value_objects::layer::Layer;
|
||||
pub use crate::value_objects::margins::Margins;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,16 @@ impl AnchorEdges {
|
|||
Self(Self::BOTTOM | Self::LEFT | Self::RIGHT)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn left_bar() -> Self {
|
||||
Self(Self::LEFT | Self::TOP | Self::BOTTOM)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn right_bar() -> Self {
|
||||
Self(Self::RIGHT | Self::TOP | Self::BOTTOM)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn with_top(mut self) -> Self {
|
||||
self.0 |= Self::TOP;
|
||||
|
|
|
|||
|
|
@ -1,34 +1,44 @@
|
|||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct WindowHeight(u32);
|
||||
pub struct WindowDimension {
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl WindowHeight {
|
||||
pub fn new(height: u32) -> Self {
|
||||
if height == 0 {
|
||||
Self::default()
|
||||
} else {
|
||||
Self(height)
|
||||
impl WindowDimension {
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
Self {
|
||||
width: if width == 0 {
|
||||
Self::default().width
|
||||
} else {
|
||||
width
|
||||
},
|
||||
height: if height == 0 {
|
||||
Self::default().height
|
||||
} else {
|
||||
height
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_raw(height: u32) -> Self {
|
||||
Self(height)
|
||||
pub const fn from_raw(width: u32, height: u32) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
|
||||
pub const fn value(&self) -> u32 {
|
||||
self.0
|
||||
pub const fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub const fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WindowHeight {
|
||||
impl Default for WindowDimension {
|
||||
fn default() -> Self {
|
||||
Self(30)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for WindowHeight {
|
||||
fn from(height: u32) -> Self {
|
||||
Self::new(height)
|
||||
Self {
|
||||
width: 20,
|
||||
height: 20,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
26
src/lib.rs
26
src/lib.rs
|
|
@ -30,6 +30,27 @@
|
|||
//! # Ok::<(), layer_shika::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! # Multi-Window Shell
|
||||
//!
|
||||
//! For multi-window shell applications:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use layer_shika::prelude::*;
|
||||
//! use std::rc::Rc;
|
||||
//!
|
||||
//! // Load Slint file with multiple shell window components
|
||||
//! let compilation_result = Rc::new(/* ... */);
|
||||
//!
|
||||
//! // Create shell with typed WindowConfig
|
||||
//! let shell = ShellComposition::new()
|
||||
//! .with_compilation_result(compilation_result)
|
||||
//! .register_shell_window("TopBar", WindowConfig::default())
|
||||
//! .build()?;
|
||||
//!
|
||||
//! shell.run()?;
|
||||
//! # Ok::<(), layer_shika::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! # Re-exports
|
||||
//!
|
||||
//! This crate re-exports commonly needed types from its dependencies:
|
||||
|
|
@ -48,6 +69,11 @@ pub use layer_shika_composition::{
|
|||
Result, ShellControl,
|
||||
};
|
||||
|
||||
pub use layer_shika_composition::{
|
||||
LayerSurfaceHandle, Shell, ShellComposition, ShellEventContext, ShellEventLoopHandle,
|
||||
ShellWindowConfigHandler, ShellWindowDefinition, ShellWindowHandle,
|
||||
};
|
||||
|
||||
pub use layer_shika_composition::{slint, slint_interpreter};
|
||||
|
||||
/// Re-exported calloop types for event loop integration
|
||||
|
|
|
|||
|
|
@ -8,20 +8,25 @@
|
|||
|
||||
#![allow(clippy::pub_use)]
|
||||
|
||||
// Core API types
|
||||
pub use crate::{
|
||||
App, Error, EventContext, EventLoopHandle, LayerShika, PopupWindow, Result, ShellControl,
|
||||
};
|
||||
|
||||
// Domain value objects
|
||||
pub use crate::{
|
||||
LayerSurfaceHandle, Shell, ShellComposition, ShellEventContext, ShellEventLoopHandle,
|
||||
ShellWindowConfigHandler, ShellWindowDefinition, ShellWindowHandle,
|
||||
};
|
||||
|
||||
pub use crate::{
|
||||
AnchorEdges, KeyboardInteractivity, Layer, OutputGeometry, OutputHandle, OutputInfo,
|
||||
OutputPolicy, OutputRegistry, PopupHandle, PopupPlacement, PopupPositioningMode, PopupRequest,
|
||||
PopupSize,
|
||||
};
|
||||
|
||||
// Event loop types
|
||||
pub use layer_shika_composition::prelude::{
|
||||
Anchor, Margins, ScaleFactor, WindowConfig, WindowDimension,
|
||||
};
|
||||
|
||||
pub use crate::calloop;
|
||||
|
||||
// UI framework re-exports
|
||||
pub use crate::{slint, slint_interpreter};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue