use crate::event_loop::{EventLoopHandleBase, FromAppState}; use crate::popup_builder::PopupBuilder; use crate::shell_runtime::{DEFAULT_WINDOW_NAME, ShellRuntime}; use crate::value_conversion::IntoValue; use crate::{Error, Result}; use layer_shika_adapters::errors::EventLoopError; use layer_shika_adapters::platform::calloop::channel; use layer_shika_adapters::platform::slint::ComponentHandle; use layer_shika_adapters::platform::slint_interpreter::{ CompilationResult, ComponentDefinition, ComponentInstance, Value, }; use layer_shika_adapters::{ AppState, PopupManager, WaylandWindowConfig, WindowState, WindowingSystemFacade, }; 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::popup_positioning_mode::PopupPositioningMode; use layer_shika_domain::value_objects::popup_request::{ PopupHandle, PopupPlacement, PopupRequest, PopupSize, }; use std::cell::Cell; use std::cell::RefCell; use std::rc::Rc; pub enum PopupCommand { Show(PopupRequest), Close(PopupHandle), Resize { handle: PopupHandle, width: f32, height: f32, }, } #[derive(Clone)] pub struct ShellControl { sender: channel::Sender, } impl ShellControl { pub fn new(sender: channel::Sender) -> Self { Self { sender } } pub fn show_popup(&self, request: &PopupRequest) -> Result<()> { self.sender .send(PopupCommand::Show(request.clone())) .map_err(|_| { Error::Domain(DomainError::Configuration { message: "Failed to send popup show command: channel closed".to_string(), }) }) } pub fn show_popup_at_cursor(&self, component: impl Into) -> Result<()> { let request = PopupRequest::builder(component.into()) .placement(PopupPlacement::AtCursor) .build(); self.show_popup(&request) } pub fn show_popup_centered(&self, component: impl Into) -> Result<()> { let request = PopupRequest::builder(component.into()) .placement(PopupPlacement::AtCursor) .mode(PopupPositioningMode::Center) .build(); self.show_popup(&request) } pub fn show_popup_at_position( &self, component: impl Into, x: f32, y: f32, ) -> Result<()> { let request = PopupRequest::builder(component.into()) .placement(PopupPlacement::AtPosition { x, y }) .build(); self.show_popup(&request) } pub fn close_popup(&self, handle: PopupHandle) -> Result<()> { self.sender.send(PopupCommand::Close(handle)).map_err(|_| { Error::Domain(DomainError::Configuration { message: "Failed to send popup close command: channel closed".to_string(), }) }) } pub fn resize_popup(&self, handle: PopupHandle, width: f32, height: f32) -> Result<()> { self.sender .send(PopupCommand::Resize { handle, width, height, }) .map_err(|_| { Error::Domain(DomainError::Configuration { message: "Failed to send popup resize command: channel closed".to_string(), }) }) } } pub type EventLoopHandle = EventLoopHandleBase>; pub struct EventContext<'a> { app_state: &'a mut AppState, } impl<'a> FromAppState<'a> for EventContext<'a> { 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( args.first() .and_then(|v| v.clone().try_into().ok()) .unwrap_or(defaults.width), args.get(1) .and_then(|v| v.clone().try_into().ok()) .unwrap_or(defaults.height), ) } impl EventContext<'_> { #[must_use] pub fn component_instance(&self) -> Option<&ComponentInstance> { self.app_state .primary_output() .map(WindowState::component_instance) } pub fn all_component_instances(&self) -> impl Iterator { self.app_state .all_outputs() .map(WindowState::component_instance) } pub const fn output_registry(&self) -> &OutputRegistry { self.app_state.output_registry() } #[must_use] pub fn primary_output_handle(&self) -> Option { self.app_state.primary_output_handle() } #[must_use] pub fn active_output_handle(&self) -> Option { self.app_state.active_output_handle() } pub fn outputs(&self) -> impl Iterator { self.app_state .outputs_with_handles() .map(|(handle, window)| (handle, window.component_instance())) } pub fn get_output_component(&self, handle: OutputHandle) -> Option<&ComponentInstance> { self.app_state .get_output_by_handle(handle) .map(WindowState::component_instance) } pub fn get_output_info(&self, handle: OutputHandle) -> Option<&OutputInfo> { self.app_state.get_output_info(handle) } pub fn all_output_info(&self) -> impl Iterator { self.app_state.all_output_info() } pub fn outputs_with_info(&self) -> impl Iterator { self.app_state .outputs_with_info() .map(|(info, window)| (info, window.component_instance())) } fn active_or_primary_output(&self) -> Option<&WindowState> { self.app_state .active_output() .or_else(|| self.app_state.primary_output()) } pub fn render_frame_if_dirty(&mut self) -> Result<()> { for window in self.app_state.all_outputs() { window.render_frame_if_dirty()?; } Ok(()) } #[must_use] pub fn compilation_result(&self) -> Option> { self.app_state .primary_output() .and_then(WindowState::compilation_result) } pub fn show_popup( &mut self, req: &PopupRequest, resize_control: Option, ) -> Result { 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", req.component ), }) })?; 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(), }) })?; log::info!( "Creating popup on {} output", if is_using_active { "active" } else { "primary" } ); let popup_manager = active_window.popup_manager().ok_or_else(|| { Error::Domain(DomainError::Configuration { message: "No popup manager available".to_string(), }) })?; let initial_dimensions = match req.size { PopupSize::Fixed { w, h } => { log::debug!("Using fixed popup size: {}x{}", w, h); (w, h) } PopupSize::Content => { log::debug!("Using content-based sizing - will measure after instance creation"); (2.0, 2.0) } }; log::debug!( "Creating popup for '{}' with dimensions {}x{} at position ({}, {}), mode: {:?}", req.component, initial_dimensions.0, initial_dimensions.1, req.placement.position().0, req.placement.position().1, req.mode ); let popup_handle = popup_manager.request_popup(req.clone(), initial_dimensions.0, initial_dimensions.1); let (instance, popup_key_cell) = Self::create_popup_instance(&definition, &popup_manager, resize_control, req)?; popup_key_cell.set(popup_handle.key()); if let Some(popup_window) = popup_manager.get_popup_window(popup_handle.key()) { popup_window.set_component_instance(instance); } else { return Err(Error::Domain(DomainError::Configuration { message: "Popup window not found after creation".to_string(), })); } Ok(popup_handle) } pub fn close_popup(&mut self, handle: PopupHandle) -> Result<()> { if let Some(active_window) = self.active_or_primary_output() { if let Some(popup_manager) = active_window.popup_manager() { popup_manager.close(handle)?; } } Ok(()) } pub fn close_current_popup(&mut self) -> Result<()> { if let Some(active_window) = self.active_or_primary_output() { if let Some(popup_manager) = active_window.popup_manager() { popup_manager.close_current_popup(); } } Ok(()) } pub fn resize_popup(&mut self, handle: PopupHandle, width: f32, height: f32) -> Result<()> { let active_window = self.active_or_primary_output().ok_or_else(|| { Error::Domain(DomainError::Configuration { message: "No active or primary output available".to_string(), }) })?; let popup_manager = active_window.popup_manager().ok_or_else(|| { Error::Domain(DomainError::Configuration { message: "No popup manager available".to_string(), }) })?; let Some((request, _serial)) = popup_manager.get_popup_info(handle.key()) else { log::debug!( "Ignoring resize request for non-existent popup with handle {:?}", handle ); return Ok(()); }; let current_size = request.size.dimensions(); let size_changed = current_size.is_none_or(|(w, h)| (w - width).abs() > 0.01 || (h - height).abs() > 0.01); if size_changed { if let Some(popup_window) = popup_manager.get_popup_window(handle.key()) { popup_window.request_resize(width, height); #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_wrap)] let logical_width = width as i32; #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_wrap)] 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, logical_height, width, height ); } } Ok(()) } fn create_popup_instance( definition: &ComponentDefinition, popup_manager: &Rc, resize_control: Option, req: &PopupRequest, ) -> Result<(ComponentInstance, Rc>)> { let instance = definition.create().map_err(|e| { Error::Domain(DomainError::Configuration { message: format!("Failed to create popup instance: {}", e), }) })?; let popup_key_cell = Rc::new(Cell::new(0)); Self::register_popup_callbacks( &instance, popup_manager, resize_control, &popup_key_cell, req, )?; instance.show().map_err(|e| { Error::Domain(DomainError::Configuration { message: format!("Failed to show popup instance: {}", e), }) })?; Ok((instance, popup_key_cell)) } fn register_popup_callbacks( instance: &ComponentInstance, popup_manager: &Rc, resize_control: Option, popup_key_cell: &Rc>, req: &PopupRequest, ) -> Result<()> { if let Some(close_callback_name) = &req.close_callback { Self::register_close_callback(instance, popup_manager, close_callback_name)?; } if let Some(resize_callback_name) = &req.resize_callback { Self::register_resize_callback( instance, popup_manager, resize_control, popup_key_cell, resize_callback_name, )?; } Ok(()) } fn register_close_callback( instance: &ComponentInstance, popup_manager: &Rc, callback_name: &str, ) -> Result<()> { let popup_manager_weak = Rc::downgrade(popup_manager); instance .set_callback(callback_name, move |_| { if let Some(popup_manager) = popup_manager_weak.upgrade() { popup_manager.close_current_popup(); } Value::Void }) .map_err(|e| { Error::Domain(DomainError::Configuration { message: format!("Failed to set '{}' callback: {}", callback_name, e), }) }) } fn register_resize_callback( instance: &ComponentInstance, popup_manager: &Rc, resize_control: Option, popup_key_cell: &Rc>, callback_name: &str, ) -> Result<()> { if let Some(control) = resize_control { Self::register_resize_with_control(instance, popup_key_cell, &control, callback_name) } else { Self::register_resize_direct(instance, popup_manager, popup_key_cell, callback_name) } } fn register_resize_with_control( instance: &ComponentInstance, popup_key_cell: &Rc>, control: &ShellControl, callback_name: &str, ) -> Result<()> { let key_cell = Rc::clone(popup_key_cell); let control = control.clone(); instance .set_callback(callback_name, move |args| { let dimensions = extract_dimensions_from_callback(args); let popup_key = key_cell.get(); log::info!( "Resize callback invoked: {}x{} for key {}", dimensions.width, dimensions.height, popup_key ); if control .resize_popup( PopupHandle::new(popup_key), dimensions.width, dimensions.height, ) .is_err() { log::error!("Failed to resize popup through control"); } Value::Void }) .map_err(|e| { Error::Domain(DomainError::Configuration { message: format!("Failed to set '{}' callback: {}", callback_name, e), }) }) } fn register_resize_direct( instance: &ComponentInstance, popup_manager: &Rc, popup_key_cell: &Rc>, callback_name: &str, ) -> Result<()> { let popup_manager_weak = Rc::downgrade(popup_manager); let key_cell = Rc::clone(popup_key_cell); instance .set_callback(callback_name, move |args| { let dimensions = extract_dimensions_from_callback(args); let popup_key = key_cell.get(); log::info!( "Resize callback invoked: {}x{} for key {}", dimensions.width, dimensions.height, popup_key ); if let Some(popup_manager) = popup_manager_weak.upgrade() { if let Some(popup_window) = popup_manager.get_popup_window(popup_key) { popup_window.request_resize(dimensions.width, dimensions.height); #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_wrap)] let logical_width = dimensions.width as i32; #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_wrap)] let logical_height = dimensions.height as i32; popup_manager.update_popup_viewport( popup_key, logical_width, logical_height, ); log::debug!( "Updated popup viewport to logical size: {}x{} (from direct resize to {}x{})", logical_width, logical_height, dimensions.width, dimensions.height ); } } Value::Void }) .map_err(|e| { Error::Domain(DomainError::Configuration { message: format!("Failed to set '{}' callback: {}", callback_name, e), }) }) } } pub struct SingleWindowShell { inner: Rc>, popup_command_sender: channel::Sender, window_name: String, } impl SingleWindowShell { pub(crate) fn new( component_definition: ComponentDefinition, compilation_result: Option>, config: WindowConfig, ) -> Result { let wayland_config = WaylandWindowConfig::from_domain_config( component_definition, compilation_result, config, ); let inner = layer_shika_adapters::WaylandWindowingSystem::new(&wayland_config)?; let facade = WindowingSystemFacade::new(inner); let inner_rc = Rc::new(RefCell::new(facade)); let (sender, receiver) = channel::channel(); let shell = Self { inner: Rc::clone(&inner_rc), popup_command_sender: sender, window_name: DEFAULT_WINDOW_NAME.to_string(), }; shell.setup_popup_command_handler(receiver)?; Ok(shell) } #[must_use] pub fn with_window_name(mut self, name: impl Into) -> Self { self.window_name = name.into(); self } #[must_use] pub fn window_name(&self) -> &str { &self.window_name } fn setup_popup_command_handler(&self, receiver: channel::Channel) -> 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 shell_context = EventContext { app_state }; match command { PopupCommand::Show(request) => { if let Err(e) = shell_context.show_popup(&request, Some(control.clone())) { log::error!("Failed to show popup: {}", e); } } PopupCommand::Close(handle) => { if let Err(e) = shell_context.close_popup(handle) { log::error!("Failed to close popup: {}", e); } } PopupCommand::Resize { handle, width, height, } => { if let Err(e) = shell_context.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 { sender: self.popup_command_sender.clone(), } } #[must_use] pub fn event_loop_handle(&self) -> EventLoopHandle { EventLoopHandle::new(Rc::downgrade(&self.inner)) } pub fn on(&self, callback_name: &str, handler: F) -> Result<()> where 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(control_clone.clone()).into_value() }) { log::error!( "Failed to register callback '{}' on component: {}", callback_name, e ); } }); Ok(()) } pub fn on_with_args(&self, callback_name: &str, handler: F) -> Result<()> where F: Fn(&[Value], 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()).into_value() }) { log::error!( "Failed to register callback '{}' on component: {}", callback_name, e ); } }); Ok(()) } pub fn on_for_output( &self, output: OutputHandle, callback_name: &str, handler: F, ) -> Result<()> where F: Fn(ShellControl) -> R + 'static, R: IntoValue, { let control = self.control(); self.with_output(output, |instance| { let control_clone = control.clone(); if let Err(e) = instance.set_callback(callback_name, move |_args| { handler(control_clone.clone()).into_value() }) { log::error!( "Failed to register callback '{}' on output {:?}: {}", callback_name, output, e ); } })?; Ok(()) } pub fn on_for_output_with_args( &self, output: OutputHandle, callback_name: &str, handler: F, ) -> Result<()> where F: Fn(&[Value], ShellControl) -> R + 'static, R: IntoValue, { let control = self.control(); self.with_output(output, |instance| { let control_clone = control.clone(); if let Err(e) = instance.set_callback(callback_name, move |args| { handler(args, control_clone.clone()).into_value() }) { log::error!( "Failed to register callback '{}' on output {:?}: {}", callback_name, output, e ); } })?; Ok(()) } #[must_use] pub fn popup(&self, component_name: impl Into) -> PopupBuilder<'_> { PopupBuilder::new(self, component_name.into()) } pub fn run(&mut self) -> Result<()> { self.inner.borrow_mut().run()?; Ok(()) } pub fn with_component_instance(&self, f: F) -> Result where F: FnOnce(&ComponentInstance) -> R, { let facade = self.inner.borrow(); let instance = facade.component_instance()?; Ok(f(instance)) } pub fn with_all_component_instances(&self, mut f: F) where F: FnMut(&ComponentInstance), { let facade = self.inner.borrow(); let system = facade.inner_ref(); for window in system.app_state().all_outputs() { f(window.component_instance()); } } pub fn with_output(&self, handle: OutputHandle, f: F) -> Result where F: FnOnce(&ComponentInstance) -> R, { let facade = self.inner.borrow(); let system = facade.inner_ref(); let window = system .app_state() .get_output_by_handle(handle) .ok_or_else(|| { Error::Domain(DomainError::Configuration { message: format!("Output with handle {:?} not found", handle), }) })?; Ok(f(window.component_instance())) } pub fn with_all_outputs(&self, mut f: F) where F: FnMut(OutputHandle, &ComponentInstance), { let facade = self.inner.borrow(); let system = facade.inner_ref(); for (handle, window) in system.app_state().outputs_with_handles() { f(handle, window.component_instance()); } } pub fn get_output_info(&self, handle: OutputHandle) -> Option { let facade = self.inner.borrow(); let system = facade.inner_ref(); system.app_state().get_output_info(handle).cloned() } pub fn all_output_info(&self) -> Vec { let facade = self.inner.borrow(); let system = facade.inner_ref(); system.app_state().all_output_info().cloned().collect() } pub fn output_registry(&self) -> OutputRegistry { let facade = self.inner.borrow(); let system = facade.inner_ref(); system.app_state().output_registry().clone() } } impl ShellRuntime for SingleWindowShell { type LoopHandle = EventLoopHandle; type Context<'a> = EventContext<'a>; fn event_loop_handle(&self) -> Self::LoopHandle { EventLoopHandle::new(Rc::downgrade(&self.inner)) } fn with_component(&self, _name: &str, mut f: F) where F: FnMut(&ComponentInstance), { let facade = self.inner.borrow(); let system = facade.inner_ref(); for window in system.app_state().all_outputs() { f(window.component_instance()); } } fn with_all_components(&self, mut f: F) where F: FnMut(&str, &ComponentInstance), { let facade = self.inner.borrow(); let system = facade.inner_ref(); for window in system.app_state().all_outputs() { f(&self.window_name, window.component_instance()); } } fn run(&mut self) -> Result<()> { self.inner.borrow_mut().run()?; Ok(()) } }