refactor: minor architecture improvement

This commit is contained in:
drendog 2025-11-02 05:35:11 +01:00
parent e8d636798d
commit 18d1391dee
Signed by: dwenya
GPG key ID: 8DD77074645332D0
33 changed files with 642 additions and 420 deletions

1
Cargo.lock generated
View file

@ -1868,6 +1868,7 @@ dependencies = [
"layer-shika-domain", "layer-shika-domain",
"log", "log",
"raw-window-handle", "raw-window-handle",
"slab",
"slint", "slint",
"slint-interpreter", "slint-interpreter",
"smithay-client-toolkit 0.20.0", "smithay-client-toolkit 0.20.0",

View file

@ -17,6 +17,7 @@ glutin = { version = "0.32.3", default-features = false, features = [
] } ] }
log = "0.4.28" log = "0.4.28"
raw-window-handle = "0.6.2" raw-window-handle = "0.6.2"
slab = "0.4"
slint = { version = "1.14.1", default-features = false, features = [ slint = { version = "1.14.1", default-features = false, features = [
"compat-1-2", "compat-1-2",
"renderer-femtovg", "renderer-femtovg",

View file

@ -18,6 +18,7 @@ glutin.workspace = true
layer-shika-domain.workspace = true layer-shika-domain.workspace = true
log.workspace = true log.workspace = true
raw-window-handle.workspace = true raw-window-handle.workspace = true
slab.workspace = true
slint.workspace = true slint.workspace = true
slint-interpreter.workspace = true slint-interpreter.workspace = true
smithay-client-toolkit.workspace = true smithay-client-toolkit.workspace = true

View file

@ -1,51 +1,172 @@
use layer_shika_domain::errors::DomainError; use layer_shika_domain::errors::DomainError;
use slint::{PlatformError, platform::SetPlatformError};
use smithay_client_toolkit::reexports::calloop;
use std::error::Error;
use std::result::Result as StdResult; use std::result::Result as StdResult;
use thiserror::Error; use thiserror::Error;
use wayland_client::backend::WaylandError; use wayland_client::backend::WaylandError;
use wayland_client::{
ConnectError, DispatchError,
globals::{BindError, GlobalError},
};
pub type Result<T> = StdResult<T, LayerShikaError>; pub type Result<T> = StdResult<T, LayerShikaError>;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum LayerShikaError { pub enum RenderingError {
#[error("Domain error: {0}")] #[error("failed to swap buffers")]
Domain(#[from] DomainError), SwapBuffers {
#[source]
source: Box<dyn Error + Send + Sync>,
},
#[error("Failed to connect to Wayland: {0}")] #[error("failed to ensure context current")]
WaylandConnection(#[from] wayland_client::ConnectError), EnsureContextCurrent {
#[source]
source: Box<dyn Error + Send + Sync>,
},
#[error("Failed to initialize Wayland globals: {0}")] #[error("rendering operation failed: {message}")]
GlobalInitialization(String), Operation { message: String },
}
#[error("Failed to dispatch Wayland event: {0}")]
WaylandDispatch(String), #[derive(Error, Debug)]
pub enum EGLError {
#[error("Failed to create EGL context: {0}")] #[error("failed to create EGL display")]
EGLContextCreation(String), DisplayCreation {
#[source]
#[error("Failed to create FemtoVG renderer: {0}")] source: Box<dyn Error + Send + Sync>,
FemtoVGRendererCreation(String), },
#[error("Failed to create Slint component: {0}")] #[error("failed to find EGL configurations")]
SlintComponentCreation(String), ConfigSelection {
#[source]
#[error("Failed to run event loop: {0}")] source: Box<dyn Error + Send + Sync>,
EventLoop(String), },
#[error("Window configuration error: {0}")] #[error("no compatible EGL configurations found")]
WindowConfiguration(String), NoCompatibleConfig,
#[error("Rendering error: {0}")] #[error("failed to create EGL context")]
Rendering(String), ContextCreation {
#[source]
#[error("Invalid input: {0}")] source: Box<dyn Error + Send + Sync>,
InvalidInput(String), },
#[error("Wayland protocol error: {0}")] #[error("failed to create window surface")]
WaylandProtocol(String), SurfaceCreation {
#[source]
#[error("Failed to set platform: {0}")] source: Box<dyn Error + Send + Sync>,
PlatformSetup(String), },
#[error("Failed to flush connection: {0}")] #[error("failed to make context current")]
ConnectionFlush(#[from] WaylandError), MakeCurrent {
#[source]
source: Box<dyn Error + Send + Sync>,
},
#[error("failed to swap buffers")]
SwapBuffers {
#[source]
source: Box<dyn Error + Send + Sync>,
},
}
#[derive(Error, Debug)]
pub enum EventLoopError {
#[error("failed to create event loop")]
Creation {
#[source]
source: calloop::Error,
},
#[error("failed to insert event source: {message}")]
InsertSource { message: String },
#[error("event loop execution failed")]
Execution {
#[source]
source: calloop::Error,
},
}
#[derive(Error, Debug)]
pub enum LayerShikaError {
#[error("domain error")]
Domain {
#[from]
source: DomainError,
},
#[error("failed to connect to Wayland display")]
WaylandConnection {
#[from]
source: ConnectError,
},
#[error("failed to initialize Wayland globals")]
GlobalInitialization {
#[source]
source: GlobalError,
},
#[error("Wayland dispatch error")]
WaylandDispatch {
#[from]
source: DispatchError,
},
#[error("failed to bind Wayland global")]
GlobalBind {
#[from]
source: BindError,
},
#[error("EGL context error")]
EGLContext {
#[from]
source: EGLError,
},
#[error("failed to create FemtoVG renderer")]
FemtoVGRendererCreation {
#[source]
source: PlatformError,
},
#[error("failed to create Slint component")]
SlintComponentCreation {
#[source]
source: PlatformError,
},
#[error("event loop error")]
EventLoop {
#[from]
source: EventLoopError,
},
#[error("window configuration error: {message}")]
WindowConfiguration { message: String },
#[error("rendering error")]
Rendering {
#[from]
source: RenderingError,
},
#[error("invalid input: {message}")]
InvalidInput { message: String },
#[error("Wayland protocol error")]
WaylandProtocol {
#[source]
source: WaylandError,
},
#[error("failed to set up Slint platform")]
PlatformSetup {
#[source]
source: SetPlatformError,
},
} }

View file

@ -1,79 +0,0 @@
use crate::wayland::{surfaces::surface_state::WindowState, shell_adapter::WaylandWindowingSystem};
use crate::{
errors::Result,
platform::calloop::{EventSource, InsertError, RegistrationToken},
};
use slint_interpreter::ComponentInstance;
use std::result::Result as StdResult;
pub struct SystemAdapter {
inner: WaylandWindowingSystem,
}
impl SystemAdapter {
#[must_use]
pub fn new(inner: WaylandWindowingSystem) -> Self {
Self { inner }
}
#[must_use]
pub fn event_loop_handle(&self) -> EventLoopAdapter {
EventLoopAdapter {
inner_system: std::ptr::addr_of!(self.inner),
}
}
pub fn run(&mut self) -> Result<()> {
self.inner.run()
}
pub const fn component_instance(&self) -> &ComponentInstance {
self.inner.component_instance()
}
}
pub struct EventLoopAdapter {
inner_system: *const WaylandWindowingSystem,
}
unsafe impl Send for EventLoopAdapter {}
unsafe impl Sync for EventLoopAdapter {}
impl EventLoopAdapter {
pub fn insert_source_with_adapter<S, F, R>(
&self,
source: S,
mut callback: F,
) -> StdResult<RegistrationToken, InsertError<S>>
where
S: EventSource<Ret = R> + 'static,
F: FnMut(S::Event, &mut S::Metadata, RuntimeStateAdapter) -> R + 'static,
{
let inner_system = unsafe { &*self.inner_system };
let loop_handle = inner_system.event_loop_handle();
loop_handle.insert_source(source, move |event, metadata, window_state| {
let runtime_state = RuntimeStateAdapter {
window_state: std::ptr::addr_of_mut!(*window_state),
};
callback(event, metadata, runtime_state)
})
}
}
pub struct RuntimeStateAdapter {
window_state: *mut WindowState,
}
impl RuntimeStateAdapter {
#[must_use]
pub fn component_instance(&self) -> &ComponentInstance {
let window_state = unsafe { &*self.window_state };
window_state.component_instance()
}
pub fn render_frame_if_dirty(&self) -> Result<()> {
let window_state = unsafe { &*self.window_state };
window_state.render_frame_if_dirty()
}
}

View file

@ -1 +0,0 @@
pub mod calloop_adapter;

View file

@ -1,10 +1,12 @@
#![allow(clippy::pub_use)] #![allow(clippy::pub_use)]
pub mod errors; pub mod errors;
pub mod event_loop;
pub mod rendering; pub mod rendering;
pub mod wayland; pub mod wayland;
pub use rendering::femtovg::popup_window::PopupWindow;
pub use rendering::slint_integration::platform::close_current_popup;
pub mod platform { pub mod platform {
pub use slint; pub use slint;
pub use slint_interpreter; pub use slint_interpreter;

View file

@ -1,4 +1,4 @@
use crate::errors::{LayerShikaError, Result}; use crate::errors::{EGLError, LayerShikaError, Result};
use glutin::{ use glutin::{
api::egl::{ api::egl::{
config::Config, config::Config,
@ -15,10 +15,10 @@ use glutin::{
use raw_window_handle::{ use raw_window_handle::{
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
}; };
use slint::{platform::femtovg_renderer::OpenGLInterface, PhysicalSize}; use slint::{PhysicalSize, platform::femtovg_renderer::OpenGLInterface};
use std::{ use std::{
error::Error, error::Error,
ffi::{self, c_void, CStr}, ffi::{self, CStr, c_void},
num::NonZeroU32, num::NonZeroU32,
ptr::NonNull, ptr::NonNull,
result::Result as StdResult, result::Result as StdResult,
@ -63,38 +63,24 @@ impl EGLContextBuilder {
self self
} }
#[must_use]
#[allow(dead_code)]
pub const fn with_config_template(mut self, config_template: ConfigTemplateBuilder) -> Self {
self.config_template = Some(config_template);
self
}
#[must_use]
#[allow(dead_code)]
pub const fn with_context_attributes(
mut self,
context_attributes: ContextAttributesBuilder,
) -> Self {
self.context_attributes = Some(context_attributes);
self
}
pub fn build(self) -> Result<EGLContext> { pub fn build(self) -> Result<EGLContext> {
let display_id = self let display_id = self
.display_id .display_id
.ok_or_else(|| LayerShikaError::InvalidInput("Display ID is required".into()))?; .ok_or_else(|| LayerShikaError::InvalidInput {
message: "Display ID is required".into(),
})?;
let surface_id = self let surface_id = self
.surface_id .surface_id
.ok_or_else(|| LayerShikaError::InvalidInput("Surface ID is required".into()))?; .ok_or_else(|| LayerShikaError::InvalidInput {
let size = self message: "Surface ID is required".into(),
.size })?;
.ok_or_else(|| LayerShikaError::InvalidInput("Size is required".into()))?; let size = self.size.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Size is required".into(),
})?;
let display_handle = create_wayland_display_handle(&display_id)?; let display_handle = create_wayland_display_handle(&display_id)?;
let glutin_display = unsafe { Display::new(display_handle) }.map_err(|e| { let glutin_display = unsafe { Display::new(display_handle) }
LayerShikaError::EGLContextCreation(format!("Failed to create display: {e}")) .map_err(|e| EGLError::DisplayCreation { source: e.into() })?;
})?;
let config_template = self.config_template.unwrap_or_default(); let config_template = self.config_template.unwrap_or_default();
@ -109,7 +95,7 @@ impl EGLContextBuilder {
let context = context let context = context
.make_current(&surface) .make_current(&surface)
.map_err(|e| LayerShikaError::EGLContextCreation(format!("Unable to activate EGL context: {e}. This may indicate a problem with the graphics drivers.")))?; .map_err(|e| EGLError::MakeCurrent { source: e.into() })?;
Ok(EGLContext { surface, context }) Ok(EGLContext { surface, context })
} }
@ -123,9 +109,9 @@ impl EGLContext {
fn ensure_current(&self) -> Result<()> { fn ensure_current(&self) -> Result<()> {
if !self.context.is_current() { if !self.context.is_current() {
self.context.make_current(&self.surface).map_err(|e| { self.context
LayerShikaError::EGLContextCreation(format!("Failed to make context current: {e}")) .make_current(&self.surface)
})?; .map_err(|e| EGLError::MakeCurrent { source: e.into() })?;
} }
Ok(()) Ok(())
} }
@ -145,7 +131,9 @@ impl Drop for EGLContext {
fn create_wayland_display_handle(display_id: &ObjectId) -> Result<RawDisplayHandle> { fn create_wayland_display_handle(display_id: &ObjectId) -> Result<RawDisplayHandle> {
let display = NonNull::new(display_id.as_ptr().cast::<c_void>()).ok_or_else(|| { let display = NonNull::new(display_id.as_ptr().cast::<c_void>()).ok_or_else(|| {
LayerShikaError::InvalidInput("Failed to create NonNull pointer for display".into()) LayerShikaError::InvalidInput {
message: "Failed to create NonNull pointer for display".into(),
}
})?; })?;
let handle = WaylandDisplayHandle::new(display); let handle = WaylandDisplayHandle::new(display);
Ok(RawDisplayHandle::Wayland(handle)) Ok(RawDisplayHandle::Wayland(handle))
@ -156,10 +144,10 @@ fn select_config(
config_template: ConfigTemplateBuilder, config_template: ConfigTemplateBuilder,
) -> Result<Config> { ) -> Result<Config> {
let mut configs = unsafe { glutin_display.find_configs(config_template.build()) } let mut configs = unsafe { glutin_display.find_configs(config_template.build()) }
.map_err(|e| LayerShikaError::EGLContextCreation(format!("Failed to find configs: {e}")))?; .map_err(|e| EGLError::ConfigSelection { source: e.into() })?;
configs.next().ok_or_else(|| { configs
LayerShikaError::EGLContextCreation("No compatible EGL configurations found.".into()) .next()
}) .ok_or_else(|| EGLError::NoCompatibleConfig.into())
} }
fn create_context( fn create_context(
@ -168,12 +156,14 @@ fn create_context(
context_attributes: ContextAttributesBuilder, context_attributes: ContextAttributesBuilder,
) -> Result<NotCurrentContext> { ) -> Result<NotCurrentContext> {
unsafe { glutin_display.create_context(config, &context_attributes.build(None)) } unsafe { glutin_display.create_context(config, &context_attributes.build(None)) }
.map_err(|e| LayerShikaError::EGLContextCreation(format!("Failed to create context: {e}"))) .map_err(|e| EGLError::ContextCreation { source: e.into() }.into())
} }
fn create_surface_handle(surface_id: &ObjectId) -> Result<RawWindowHandle> { fn create_surface_handle(surface_id: &ObjectId) -> Result<RawWindowHandle> {
let surface = NonNull::new(surface_id.as_ptr().cast::<c_void>()).ok_or_else(|| { let surface = NonNull::new(surface_id.as_ptr().cast::<c_void>()).ok_or_else(|| {
LayerShikaError::InvalidInput("Failed to create NonNull pointer for surface".into()) LayerShikaError::InvalidInput {
message: "Failed to create NonNull pointer for surface".into(),
}
})?; })?;
let handle = WaylandWindowHandle::new(surface); let handle = WaylandWindowHandle::new(surface);
Ok(RawWindowHandle::Wayland(handle)) Ok(RawWindowHandle::Wayland(handle))
@ -185,18 +175,19 @@ fn create_surface(
surface_handle: RawWindowHandle, surface_handle: RawWindowHandle,
size: PhysicalSize, size: PhysicalSize,
) -> Result<Surface<WindowSurface>> { ) -> Result<Surface<WindowSurface>> {
let width = NonZeroU32::new(size.width) let width = NonZeroU32::new(size.width).ok_or_else(|| LayerShikaError::InvalidInput {
.ok_or_else(|| LayerShikaError::InvalidInput("Width cannot be zero".into()))?; message: "Width cannot be zero".into(),
})?;
let height = NonZeroU32::new(size.height) let height = NonZeroU32::new(size.height).ok_or_else(|| LayerShikaError::InvalidInput {
.ok_or_else(|| LayerShikaError::InvalidInput("Height cannot be zero".into()))?; message: "Height cannot be zero".into(),
})?;
let attrs = let attrs =
SurfaceAttributesBuilder::<WindowSurface>::new().build(surface_handle, width, height); SurfaceAttributesBuilder::<WindowSurface>::new().build(surface_handle, width, height);
unsafe { glutin_display.create_window_surface(config, &attrs) }.map_err(|e| { unsafe { glutin_display.create_window_surface(config, &attrs) }
LayerShikaError::EGLContextCreation(format!("Failed to create window surface: {e}")) .map_err(|e| EGLError::SurfaceCreation { source: e.into() }.into())
})
} }
unsafe impl OpenGLInterface for EGLContext { unsafe impl OpenGLInterface for EGLContext {
@ -206,9 +197,9 @@ unsafe impl OpenGLInterface for EGLContext {
} }
fn swap_buffers(&self) -> StdResult<(), Box<dyn Error + Send + Sync>> { fn swap_buffers(&self) -> StdResult<(), Box<dyn Error + Send + Sync>> {
self.surface.swap_buffers(&self.context).map_err(|e| { self.surface
LayerShikaError::EGLContextCreation(format!("Failed to swap buffers: {e}")).into() .swap_buffers(&self.context)
}) .map_err(|e| EGLError::SwapBuffers { source: e.into() }.into())
} }
fn resize( fn resize(

View file

@ -1,9 +1,9 @@
use crate::errors::{LayerShikaError, Result}; use crate::errors::{RenderingError, Result};
use core::ops::Deref; use core::ops::Deref;
use log::info; use log::info;
use slint::{ use slint::{
platform::{femtovg_renderer::FemtoVGRenderer, Renderer, WindowAdapter, WindowEvent},
PhysicalSize, Window, WindowSize, PhysicalSize, Window, WindowSize,
platform::{Renderer, WindowAdapter, WindowEvent, femtovg_renderer::FemtoVGRenderer},
}; };
use std::cell::Cell; use std::cell::Cell;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
@ -43,7 +43,9 @@ impl FemtoVGWindow {
) { ) {
self.renderer self.renderer
.render() .render()
.map_err(|e| LayerShikaError::Rendering(format!("Error rendering frame: {e}")))?; .map_err(|e| RenderingError::Operation {
message: format!("Error rendering frame: {e}"),
})?;
} }
Ok(()) Ok(())
} }

View file

@ -1,11 +1,12 @@
use crate::errors::{LayerShikaError, Result}; use crate::errors::{RenderingError, Result};
use crate::wayland::surfaces::popup_manager::PopupManager;
use core::ops::Deref; use core::ops::Deref;
use log::info; use log::info;
use slint::{ use slint::{
platform::{femtovg_renderer::FemtoVGRenderer, Renderer, WindowAdapter, WindowEvent},
PhysicalSize, Window, WindowSize, PhysicalSize, Window, WindowSize,
platform::{Renderer, WindowAdapter, WindowEvent, femtovg_renderer::FemtoVGRenderer},
}; };
use std::cell::Cell; use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use super::main_window::RenderState; use super::main_window::RenderState;
@ -17,6 +18,8 @@ pub struct PopupWindow {
render_state: Cell<RenderState>, render_state: Cell<RenderState>,
size: Cell<PhysicalSize>, size: Cell<PhysicalSize>,
scale_factor: Cell<f32>, scale_factor: Cell<f32>,
popup_manager: RefCell<Weak<PopupManager>>,
popup_key: Cell<Option<usize>>,
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -31,10 +34,37 @@ impl PopupWindow {
render_state: Cell::new(RenderState::Clean), render_state: Cell::new(RenderState::Clean),
size: Cell::new(PhysicalSize::default()), size: Cell::new(PhysicalSize::default()),
scale_factor: Cell::new(1.), scale_factor: Cell::new(1.),
popup_manager: RefCell::new(Weak::new()),
popup_key: Cell::new(None),
} }
}) })
} }
pub fn set_popup_manager(&self, popup_manager: Weak<PopupManager>, key: usize) {
*self.popup_manager.borrow_mut() = popup_manager;
self.popup_key.set(Some(key));
}
pub fn close_popup(&self) {
info!("Closing popup window - cleaning up resources");
if let Err(e) = self.window.hide() {
info!("Failed to hide popup window: {e}");
}
if let Some(popup_manager) = self.popup_manager.borrow().upgrade() {
if let Some(key) = self.popup_key.get() {
info!("Destroying popup with key {key}");
popup_manager.destroy_popup(key);
}
}
*self.popup_manager.borrow_mut() = Weak::new();
self.popup_key.set(None);
info!("Popup window cleanup complete");
}
pub fn render_frame_if_dirty(&self) -> Result<()> { pub fn render_frame_if_dirty(&self) -> Result<()> {
if matches!( if matches!(
self.render_state.replace(RenderState::Clean), self.render_state.replace(RenderState::Clean),
@ -45,9 +75,11 @@ impl PopupWindow {
self.size.get(), self.size.get(),
self.scale_factor.get() self.scale_factor.get()
); );
self.renderer.render().map_err(|e| { self.renderer
LayerShikaError::Rendering(format!("Error rendering popup frame: {e}")) .render()
})?; .map_err(|e| RenderingError::Operation {
message: format!("Error rendering popup frame: {e}"),
})?;
info!("Popup frame rendered successfully"); info!("Popup frame rendered successfully");
} }
Ok(()) Ok(())
@ -96,3 +128,9 @@ impl Deref for PopupWindow {
&self.window &self.window
} }
} }
impl Drop for PopupWindow {
fn drop(&mut self) {
info!("PopupWindow being dropped - resources will be released");
}
}

View file

@ -1,28 +1,51 @@
use slint::{ use slint::{
platform::{Platform, WindowAdapter},
PlatformError, PlatformError,
platform::{Platform, WindowAdapter},
}; };
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use crate::rendering::femtovg::main_window::FemtoVGWindow; use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::rendering::femtovg::popup_window::PopupWindow;
type PopupCreator = dyn Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError>; type PopupCreator = dyn Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError>;
thread_local! {
static CURRENT_PLATFORM: RefCell<Option<Weak<CustomSlintPlatform>>> = const { RefCell::new(None) };
}
pub fn close_current_popup() {
CURRENT_PLATFORM.with(|platform| {
if let Some(weak_platform) = platform.borrow().as_ref() {
if let Some(strong_platform) = weak_platform.upgrade() {
strong_platform.close_current_popup();
}
}
});
}
pub struct CustomSlintPlatform { pub struct CustomSlintPlatform {
main_window: Weak<FemtoVGWindow>, main_window: Weak<FemtoVGWindow>,
popup_creator: RefCell<Option<Rc<PopupCreator>>>, popup_creator: RefCell<Option<Rc<PopupCreator>>>,
first_call: Cell<bool>, first_call: Cell<bool>,
last_popup: RefCell<Option<Weak<PopupWindow>>>,
} }
impl CustomSlintPlatform { impl CustomSlintPlatform {
#[must_use] #[must_use]
pub fn new(window: &Rc<FemtoVGWindow>) -> Self { pub fn new(window: &Rc<FemtoVGWindow>) -> Rc<Self> {
Self { let platform = Rc::new(Self {
main_window: Rc::downgrade(window), main_window: Rc::downgrade(window),
popup_creator: RefCell::new(None), popup_creator: RefCell::new(None),
first_call: Cell::new(true), first_call: Cell::new(true),
} last_popup: RefCell::new(None),
});
CURRENT_PLATFORM.with(|current| {
*current.borrow_mut() = Some(Rc::downgrade(&platform));
});
platform
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -32,6 +55,19 @@ impl CustomSlintPlatform {
{ {
*self.popup_creator.borrow_mut() = Some(Rc::new(creator)); *self.popup_creator.borrow_mut() = Some(Rc::new(creator));
} }
pub fn set_last_popup(&self, popup: &Rc<PopupWindow>) {
*self.last_popup.borrow_mut() = Some(Rc::downgrade(popup));
}
pub fn close_current_popup(&self) {
if let Some(weak_popup) = self.last_popup.borrow().as_ref() {
if let Some(popup) = weak_popup.upgrade() {
popup.close_popup();
}
}
*self.last_popup.borrow_mut() = None;
}
} }
impl Platform for CustomSlintPlatform { impl Platform for CustomSlintPlatform {

View file

@ -1,4 +1,6 @@
use layer_shika_domain::config::{AnchorEdges, Layer, Margins, WindowConfig as DomainWindowConfig}; use layer_shika_domain::prelude::{
AnchorEdges, Layer, Margins, WindowConfig as DomainWindowConfig,
};
use slint_interpreter::ComponentDefinition; use slint_interpreter::ComponentDefinition;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{ use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::{self}, zwlr_layer_shell_v1::{self},

View file

@ -1,10 +1,9 @@
use crate::errors::{LayerShikaError, Result}; use crate::errors::Result;
use std::rc::Rc; use std::rc::Rc;
use wayland_client::{Connection, EventQueue}; use wayland_client::{Connection, EventQueue};
pub fn initialize_wayland<S>() -> Result<(Rc<Connection>, EventQueue<S>)> { pub fn initialize_wayland<S>() -> Result<(Rc<Connection>, EventQueue<S>)> {
let connection = let connection = Rc::new(Connection::connect_to_env()?);
Rc::new(Connection::connect_to_env().map_err(LayerShikaError::WaylandConnection)?);
let event_queue = connection.new_event_queue(); let event_queue = connection.new_event_queue();
Ok((connection, event_queue)) Ok((connection, event_queue))
} }

View file

@ -1,9 +1,9 @@
use crate::wayland::surfaces::surface_state::WindowState;
use crate::impl_empty_dispatch; use crate::impl_empty_dispatch;
use crate::wayland::surfaces::surface_state::WindowState;
use log::info; use log::info;
use slint::{ use slint::{
platform::{PointerEventButton, WindowEvent},
PhysicalSize, PhysicalSize,
platform::{PointerEventButton, WindowEvent},
}; };
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{ use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::ZwlrLayerShellV1, zwlr_layer_shell_v1::ZwlrLayerShellV1,
@ -11,6 +11,7 @@ use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
}; };
use wayland_client::WEnum; use wayland_client::WEnum;
use wayland_client::{ use wayland_client::{
Connection, Dispatch, Proxy, QueueHandle,
globals::GlobalListContents, globals::GlobalListContents,
protocol::{ protocol::{
wl_compositor::WlCompositor, wl_compositor::WlCompositor,
@ -20,7 +21,6 @@ use wayland_client::{
wl_seat::WlSeat, wl_seat::WlSeat,
wl_surface::WlSurface, wl_surface::WlSurface,
}, },
Connection, Dispatch, Proxy, QueueHandle,
}; };
use wayland_protocols::wp::fractional_scale::v1::client::{ use wayland_protocols::wp::fractional_scale::v1::client::{
wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,
@ -138,7 +138,9 @@ impl Dispatch<WlOutput, ()> for WindowState {
model, model,
transform, transform,
} => { } => {
info!("WlOutput geometry: x={x}, y={y}, physical_width={physical_width}, physical_height={physical_height}, subpixel={subpixel:?}, make={make:?}, model={model:?}, transform={transform:?}"); info!(
"WlOutput geometry: x={x}, y={y}, physical_width={physical_width}, physical_height={physical_height}, subpixel={subpixel:?}, make={make:?}, model={model:?}, transform={transform:?}"
);
} }
wl_output::Event::Done => { wl_output::Event::Done => {
info!("WlOutput done"); info!("WlOutput done");
@ -270,16 +272,16 @@ impl Dispatch<XdgPopup, ()> for WindowState {
xdg_popup::Event::PopupDone => { xdg_popup::Event::PopupDone => {
info!("XdgPopup dismissed by compositor"); info!("XdgPopup dismissed by compositor");
let popup_id = xdg_popup.id(); let popup_id = xdg_popup.id();
let popup_index = state let popup_key = state
.popup_manager() .popup_manager()
.as_ref() .as_ref()
.and_then(|pm| pm.find_popup_index_by_xdg_popup_id(&popup_id)); .and_then(|pm| pm.find_popup_key_by_xdg_popup_id(&popup_id));
if let Some(index) = popup_index { if let Some(key) = popup_key {
info!("Destroying popup at index {index}"); info!("Destroying popup with key {key}");
state.clear_active_window_if_popup(index); state.clear_active_window_if_popup(key);
if let Some(popup_manager) = &state.popup_manager() { if let Some(popup_manager) = &state.popup_manager() {
popup_manager.destroy_popup(index); popup_manager.destroy_popup(key);
} }
} }
} }

View file

@ -23,8 +23,7 @@ macro_rules! bind_globals {
($global_list:expr, $queue_handle:expr, $(($interface:ty, $name:ident, $version:expr)),+) => { ($global_list:expr, $queue_handle:expr, $(($interface:ty, $name:ident, $version:expr)),+) => {
{ {
$( $(
let $name: $interface = $global_list.bind($queue_handle, $version, ()) let $name: $interface = $global_list.bind($queue_handle, $version, ())?;
.map_err(|e| LayerShikaError::WaylandDispatch(e.to_string()))?;
)+ )+
Ok::<($($interface,)+), LayerShikaError>(($($name,)+)) Ok::<($($interface,)+), LayerShikaError>(($($name,)+))
} }

View file

@ -29,7 +29,7 @@ impl GlobalContext {
) -> Result<Self, LayerShikaError> { ) -> Result<Self, LayerShikaError> {
let global_list = registry_queue_init::<WindowState>(connection) let global_list = registry_queue_init::<WindowState>(connection)
.map(|(global_list, _)| global_list) .map(|(global_list, _)| global_list)
.map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?; .map_err(|e| LayerShikaError::GlobalInitialization { source: e })?;
let (compositor, output, layer_shell, seat) = bind_globals!( let (compositor, output, layer_shell, seat) = bind_globals!(
&global_list, &global_list,

View file

@ -1,29 +1,36 @@
use crate::wayland::{ use crate::wayland::{
config::{LayerSurfaceParams, WaylandWindowConfig}, config::{LayerSurfaceParams, WaylandWindowConfig},
globals::context::GlobalContext, globals::context::GlobalContext,
surfaces::popup_manager::{PopupContext, PopupManager},
surfaces::{surface_builder::WindowStateBuilder, surface_state::WindowState},
surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams}, surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams},
}; surfaces::popup_manager::{PopupContext, PopupManager},
use crate::{ surfaces::{
errors::{LayerShikaError, Result}, surface_builder::WindowStateBuilder,
rendering::{ surface_state::{SharedPointerSerial, WindowState},
egl::context::EGLContext, femtovg::main_window::FemtoVGWindow, slint_integration::platform::CustomSlintPlatform,
}, },
}; };
use crate::{
errors::{EventLoopError, LayerShikaError, RenderingError, Result},
rendering::{
egl::context::EGLContext, femtovg::main_window::FemtoVGWindow,
slint_integration::platform::CustomSlintPlatform,
},
};
use core::result::Result as CoreResult;
use layer_shika_domain::errors::DomainError;
use layer_shika_domain::ports::windowing::WindowingSystemPort;
use log::{error, info}; use log::{error, info};
use slint::{ use slint::{
platform::{femtovg_renderer::FemtoVGRenderer, update_timers_and_animations, WindowAdapter},
LogicalPosition, PhysicalSize, PlatformError, WindowPosition, LogicalPosition, PhysicalSize, PlatformError, WindowPosition,
platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, update_timers_and_animations},
}; };
use slint_interpreter::ComponentInstance; use slint_interpreter::ComponentInstance;
use smithay_client_toolkit::reexports::calloop::{ use smithay_client_toolkit::reexports::calloop::{
generic::Generic, EventLoop, Interest, LoopHandle, Mode, PostAction, EventLoop, Interest, LoopHandle, Mode, PostAction, generic::Generic,
}; };
use std::rc::Rc; use std::rc::Rc;
use wayland_client::{ use wayland_client::{
protocol::{wl_display::WlDisplay, wl_surface::WlSurface},
Connection, EventQueue, Proxy, Connection, EventQueue, Proxy,
protocol::{wl_display::WlDisplay, wl_surface::WlSurface},
}; };
pub struct WaylandWindowingSystem { pub struct WaylandWindowingSystem {
@ -40,7 +47,7 @@ impl WaylandWindowingSystem {
let (connection, event_queue) = Self::init_wayland_connection()?; let (connection, event_queue) = Self::init_wayland_connection()?;
let (state, global_ctx, platform) = Self::init_state(config, &connection, &event_queue)?; let (state, global_ctx, platform) = Self::init_state(config, &connection, &event_queue)?;
let event_loop = let event_loop =
EventLoop::try_new().map_err(|e| LayerShikaError::EventLoop(e.to_string()))?; EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?;
let popup_context = PopupContext::new( let popup_context = PopupContext::new(
global_ctx.compositor, global_ctx.compositor,
@ -53,8 +60,15 @@ impl WaylandWindowingSystem {
); );
let popup_manager = Rc::new(PopupManager::new(popup_context, state.scale_factor())); let popup_manager = Rc::new(PopupManager::new(popup_context, state.scale_factor()));
let shared_serial = Rc::new(SharedPointerSerial::new());
Self::setup_popup_creator(&popup_manager, &platform, &state, &event_queue); Self::setup_popup_creator(
&popup_manager,
&platform,
&state,
&event_queue,
&shared_serial,
);
Ok(Self { Ok(Self {
state, state,
@ -67,13 +81,13 @@ impl WaylandWindowingSystem {
system system
.state .state
.set_popup_manager(Rc::clone(&system.popup_manager)); .set_popup_manager(Rc::clone(&system.popup_manager));
system.state.set_shared_pointer_serial(shared_serial);
system system
}) })
} }
fn init_wayland_connection() -> Result<(Rc<Connection>, EventQueue<WindowState>)> { fn init_wayland_connection() -> Result<(Rc<Connection>, EventQueue<WindowState>)> {
let connection = let connection = Rc::new(Connection::connect_to_env()?);
Rc::new(Connection::connect_to_env().map_err(LayerShikaError::WaylandConnection)?);
let event_queue = connection.new_event_queue(); let event_queue = connection.new_event_queue();
Ok((connection, event_queue)) Ok((connection, event_queue))
} }
@ -83,8 +97,7 @@ impl WaylandWindowingSystem {
connection: &Connection, connection: &Connection,
event_queue: &EventQueue<WindowState>, event_queue: &EventQueue<WindowState>,
) -> Result<(WindowState, GlobalContext, Rc<CustomSlintPlatform>)> { ) -> Result<(WindowState, GlobalContext, Rc<CustomSlintPlatform>)> {
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle()) let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?;
.map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?;
let layer_surface_params = LayerSurfaceParams { let layer_surface_params = LayerSurfaceParams {
anchor: config.anchor, anchor: config.anchor,
@ -107,22 +120,20 @@ impl WaylandWindowingSystem {
let surface_ctx = SurfaceCtx::setup(&setup_params, &layer_surface_params); let surface_ctx = SurfaceCtx::setup(&setup_params, &layer_surface_params);
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
let output = Rc::new(global_ctx.output.clone());
let window = let window =
Self::initialize_renderer(&surface_ctx.surface, &connection.display(), &config) Self::initialize_renderer(&surface_ctx.surface, &connection.display(), &config)?;
.map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?;
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
let mut builder = WindowStateBuilder::new() let mut builder = WindowStateBuilder::new()
.with_component_definition(config.component_definition) .with_component_definition(config.component_definition)
.with_surface(Rc::clone(&surface_ctx.surface)) .with_surface(Rc::clone(&surface_ctx.surface))
.with_layer_surface(Rc::clone(&surface_ctx.layer_surface)) .with_layer_surface(Rc::clone(&surface_ctx.layer_surface))
.with_pointer(Rc::clone(&pointer))
.with_output(Rc::clone(&output))
.with_scale_factor(config.scale_factor) .with_scale_factor(config.scale_factor)
.with_height(config.height) .with_height(config.height)
.with_exclusive_zone(config.exclusive_zone) .with_exclusive_zone(config.exclusive_zone)
.with_connection(Rc::new(connection.clone())) .with_connection(Rc::new(connection.clone()))
.with_pointer(Rc::clone(&pointer))
.with_window(window); .with_window(window);
if let Some(fs) = &surface_ctx.fractional_scale { if let Some(fs) = &surface_ctx.fractional_scale {
@ -133,9 +144,12 @@ impl WaylandWindowingSystem {
builder = builder.with_viewport(Rc::clone(vp)); builder = builder.with_viewport(Rc::clone(vp));
} }
let (state, platform) = builder let (state, platform) =
.build() builder
.map_err(|e| LayerShikaError::WindowConfiguration(e.to_string()))?; .build()
.map_err(|e| LayerShikaError::WindowConfiguration {
message: e.to_string(),
})?;
Ok((state, global_ctx, platform)) Ok((state, global_ctx, platform))
} }
@ -145,6 +159,7 @@ impl WaylandWindowingSystem {
platform: &Rc<CustomSlintPlatform>, platform: &Rc<CustomSlintPlatform>,
state: &WindowState, state: &WindowState,
event_queue: &EventQueue<WindowState>, event_queue: &EventQueue<WindowState>,
shared_serial: &Rc<SharedPointerSerial>,
) { ) {
if !popup_manager.has_xdg_shell() { if !popup_manager.has_xdg_shell() {
info!("xdg-shell not available, popups will not be supported"); info!("xdg-shell not available, popups will not be supported");
@ -154,16 +169,25 @@ impl WaylandWindowingSystem {
info!("Setting up popup creator with xdg-shell support"); info!("Setting up popup creator with xdg-shell support");
let popup_manager_clone = Rc::clone(popup_manager); let popup_manager_clone = Rc::clone(popup_manager);
let platform_weak = Rc::downgrade(platform);
let layer_surface = state.layer_surface(); let layer_surface = state.layer_surface();
let queue_handle = event_queue.handle(); let queue_handle = event_queue.handle();
let serial_holder = Rc::clone(shared_serial);
platform.set_popup_creator(move || { platform.set_popup_creator(move || {
info!("Popup creator called! Creating popup window..."); info!("Popup creator called! Creating popup window...");
let result = popup_manager_clone let serial = serial_holder.get();
.create_popup(&queue_handle, &layer_surface, 0)
.map(|w| w as Rc<dyn WindowAdapter>) let popup_window = popup_manager_clone
.map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}"))); .create_popup(&queue_handle, &layer_surface, serial)
.map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")))?;
if let Some(platform) = platform_weak.upgrade() {
platform.set_last_popup(&popup_window);
}
let result = Ok(popup_window as Rc<dyn WindowAdapter>);
match &result { match &result {
Ok(_) => info!("Popup created successfully"), Ok(_) => info!("Popup created successfully"),
@ -185,11 +209,10 @@ impl WaylandWindowingSystem {
.with_display_id(display.id()) .with_display_id(display.id())
.with_surface_id(surface.id()) .with_surface_id(surface.id())
.with_size(init_size) .with_size(init_size)
.build() .build()?;
.map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?;
let renderer = FemtoVGRenderer::new(context) let renderer = FemtoVGRenderer::new(context)
.map_err(|e| LayerShikaError::FemtoVGRendererCreation(e.to_string()))?; .map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?;
let femtovg_window = FemtoVGWindow::new(renderer); let femtovg_window = FemtoVGWindow::new(renderer);
femtovg_window.set_size(slint::WindowSize::Physical(init_size)); femtovg_window.set_size(slint::WindowSize::Physical(init_size));
@ -207,21 +230,18 @@ impl WaylandWindowingSystem {
info!("Starting WindowingSystem main loop"); info!("Starting WindowingSystem main loop");
info!("Processing initial Wayland configuration events"); info!("Processing initial Wayland configuration events");
while self while self.event_queue.blocking_dispatch(&mut self.state)? > 0 {
.event_queue
.blocking_dispatch(&mut self.state)
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?
> 0
{
self.connection self.connection
.flush() .flush()
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?; .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
update_timers_and_animations(); update_timers_and_animations();
self.state self.state
.window() .window()
.render_frame_if_dirty() .render_frame_if_dirty()
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?; .map_err(|e| RenderingError::Operation {
message: e.to_string(),
})?;
} }
self.setup_wayland_event_source()?; self.setup_wayland_event_source()?;
@ -238,7 +258,9 @@ impl WaylandWindowingSystem {
error!("Error processing events: {e}"); error!("Error processing events: {e}");
} }
}) })
.map_err(|e| LayerShikaError::EventLoop(e.to_string())) .map_err(|e| EventLoopError::Execution { source: e })?;
Ok(())
} }
fn setup_wayland_event_source(&self) -> Result<()> { fn setup_wayland_event_source(&self) -> Result<()> {
@ -250,7 +272,9 @@ impl WaylandWindowingSystem {
Generic::new(connection, Interest::READ, Mode::Level), Generic::new(connection, Interest::READ, Mode::Level),
move |_, _connection, _shared_data| Ok(PostAction::Continue), move |_, _connection, _shared_data| Ok(PostAction::Continue),
) )
.map_err(|e| LayerShikaError::EventLoop(e.to_string()))?; .map_err(|e| EventLoopError::InsertSource {
message: format!("{e:?}"),
})?;
Ok(()) Ok(())
} }
@ -264,27 +288,29 @@ impl WaylandWindowingSystem {
if let Some(guard) = event_queue.prepare_read() { if let Some(guard) = event_queue.prepare_read() {
guard guard
.read() .read()
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?; .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
} }
event_queue event_queue.dispatch_pending(shared_data)?;
.dispatch_pending(shared_data)
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
update_timers_and_animations(); update_timers_and_animations();
shared_data shared_data
.window() .window()
.render_frame_if_dirty() .render_frame_if_dirty()
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?; .map_err(|e| RenderingError::Operation {
message: e.to_string(),
})?;
popup_manager popup_manager
.render_popups() .render_popups()
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?; .map_err(|e| RenderingError::Operation {
message: e.to_string(),
})?;
connection connection
.flush() .flush()
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?; .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
Ok(()) Ok(())
} }
@ -293,12 +319,15 @@ impl WaylandWindowingSystem {
self.state.component_instance() self.state.component_instance()
} }
#[allow(dead_code)]
pub(crate) fn window(&self) -> Rc<FemtoVGWindow> {
self.state.window()
}
pub const fn state(&self) -> &WindowState { pub const fn state(&self) -> &WindowState {
&self.state &self.state
} }
} }
impl WindowingSystemPort for WaylandWindowingSystem {
fn run(&mut self) -> CoreResult<(), DomainError> {
WaylandWindowingSystem::run(self).map_err(|e| DomainError::Adapter {
source: Box::new(e),
})
}
}

View file

@ -6,8 +6,8 @@ use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
}; };
use std::rc::Rc; use std::rc::Rc;
use wayland_client::{ use wayland_client::{
protocol::{wl_compositor::WlCompositor, wl_output::WlOutput, wl_surface::WlSurface},
QueueHandle, QueueHandle,
protocol::{wl_compositor::WlCompositor, wl_output::WlOutput, wl_surface::WlSurface},
}; };
use wayland_protocols::wp::fractional_scale::v1::client::{ use wayland_protocols::wp::fractional_scale::v1::client::{
wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,

View file

@ -2,6 +2,7 @@ use crate::errors::{LayerShikaError, Result};
use crate::rendering::egl::context::EGLContext; use crate::rendering::egl::context::EGLContext;
use crate::rendering::femtovg::popup_window::PopupWindow; use crate::rendering::femtovg::popup_window::PopupWindow;
use log::info; use log::info;
use slab::Slab;
use slint::{platform::femtovg_renderer::FemtoVGRenderer, PhysicalSize, WindowSize}; use slint::{platform::femtovg_renderer::FemtoVGRenderer, PhysicalSize, WindowSize};
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
use std::cell::RefCell; use std::cell::RefCell;
@ -25,20 +26,18 @@ pub struct PopupContext {
fractional_scale_manager: Option<WpFractionalScaleManagerV1>, fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
viewporter: Option<WpViewporter>, viewporter: Option<WpViewporter>,
display: WlDisplay, display: WlDisplay,
#[allow(dead_code)]
connection: Rc<Connection>,
} }
impl PopupContext { impl PopupContext {
#[must_use] #[must_use]
pub const fn new( pub fn new(
compositor: WlCompositor, compositor: WlCompositor,
xdg_wm_base: Option<XdgWmBase>, xdg_wm_base: Option<XdgWmBase>,
seat: WlSeat, seat: WlSeat,
fractional_scale_manager: Option<WpFractionalScaleManagerV1>, fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
viewporter: Option<WpViewporter>, viewporter: Option<WpViewporter>,
display: WlDisplay, display: WlDisplay,
connection: Rc<Connection>, _connection: Rc<Connection>,
) -> Self { ) -> Self {
Self { Self {
compositor, compositor,
@ -47,7 +46,6 @@ impl PopupContext {
fractional_scale_manager, fractional_scale_manager,
viewporter, viewporter,
display, display,
connection,
} }
} }
} }
@ -59,7 +57,7 @@ struct ActivePopup {
pub struct PopupManager { pub struct PopupManager {
context: PopupContext, context: PopupContext,
popups: RefCell<Vec<ActivePopup>>, popups: RefCell<Slab<ActivePopup>>,
current_scale_factor: RefCell<f32>, current_scale_factor: RefCell<f32>,
current_output_size: RefCell<PhysicalSize>, current_output_size: RefCell<PhysicalSize>,
} }
@ -69,7 +67,7 @@ impl PopupManager {
pub const fn new(context: PopupContext, initial_scale_factor: f32) -> Self { pub const fn new(context: PopupContext, initial_scale_factor: f32) -> Self {
Self { Self {
context, context,
popups: RefCell::new(Vec::new()), popups: RefCell::new(Slab::new()),
current_scale_factor: RefCell::new(initial_scale_factor), current_scale_factor: RefCell::new(initial_scale_factor),
current_output_size: RefCell::new(PhysicalSize::new(0, 0)), current_output_size: RefCell::new(PhysicalSize::new(0, 0)),
} }
@ -84,18 +82,22 @@ impl PopupManager {
} }
pub fn create_popup( pub fn create_popup(
&self, self: &Rc<Self>,
queue_handle: &QueueHandle<WindowState>, queue_handle: &QueueHandle<WindowState>,
parent_layer_surface: &ZwlrLayerSurfaceV1, parent_layer_surface: &ZwlrLayerSurfaceV1,
last_pointer_serial: u32, last_pointer_serial: u32,
) -> Result<Rc<PopupWindow>> { ) -> Result<Rc<PopupWindow>> {
let xdg_wm_base = self.context.xdg_wm_base.as_ref().ok_or_else(|| { let xdg_wm_base = self.context.xdg_wm_base.as_ref().ok_or_else(|| {
LayerShikaError::WaylandProtocol("xdg-shell not available for popups".into()) LayerShikaError::WindowConfiguration {
message: "xdg-shell not available for popups".into(),
}
})?; })?;
let scale_factor = *self.current_scale_factor.borrow(); let scale_factor = *self.current_scale_factor.borrow();
let output_size = *self.current_output_size.borrow(); let output_size = *self.current_output_size.borrow();
info!("Creating popup window with scale factor {scale_factor} and output size {output_size:?}"); info!(
"Creating popup window with scale factor {scale_factor} and output size {output_size:?}"
);
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_precision_loss)]
let logical_size = slint::LogicalSize::new( let logical_size = slint::LogicalSize::new(
@ -129,28 +131,28 @@ impl PopupManager {
.with_display_id(self.context.display.id()) .with_display_id(self.context.display.id())
.with_surface_id(popup_surface.surface.id()) .with_surface_id(popup_surface.surface.id())
.with_size(popup_size) .with_size(popup_size)
.build() .build()?;
.map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?;
let renderer = FemtoVGRenderer::new(context) let renderer = FemtoVGRenderer::new(context)
.map_err(|e| LayerShikaError::FemtoVGRendererCreation(e.to_string()))?; .map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?;
let popup_window = PopupWindow::new(renderer); let popup_window = PopupWindow::new(renderer);
popup_window.set_scale_factor(scale_factor); popup_window.set_scale_factor(scale_factor);
popup_window.set_size(WindowSize::Logical(logical_size)); popup_window.set_size(WindowSize::Logical(logical_size));
info!("Popup window created successfully"); let key = self.popups.borrow_mut().insert(ActivePopup {
self.popups.borrow_mut().push(ActivePopup {
surface: popup_surface, surface: popup_surface,
window: Rc::clone(&popup_window), window: Rc::clone(&popup_window),
}); });
popup_window.set_popup_manager(Rc::downgrade(self), key);
info!("Popup window created successfully with key {key}");
Ok(popup_window) Ok(popup_window)
} }
pub fn render_popups(&self) -> Result<()> { pub fn render_popups(&self) -> Result<()> {
for popup in self.popups.borrow().iter() { for (_key, popup) in self.popups.borrow().iter() {
popup.window.render_frame_if_dirty()?; popup.window.render_frame_if_dirty()?;
} }
Ok(()) Ok(())
@ -160,61 +162,51 @@ impl PopupManager {
self.context.xdg_wm_base.is_some() self.context.xdg_wm_base.is_some()
} }
#[allow(dead_code)]
pub fn popup_count(&self) -> usize {
self.popups.borrow().len()
}
pub fn mark_all_popups_dirty(&self) { pub fn mark_all_popups_dirty(&self) {
for popup in self.popups.borrow().iter() { for (_key, popup) in self.popups.borrow().iter() {
popup.window.request_redraw(); popup.window.request_redraw();
} }
} }
pub fn find_popup_index_by_surface_id(&self, surface_id: &ObjectId) -> Option<usize> { pub fn find_popup_key_by_surface_id(&self, surface_id: &ObjectId) -> Option<usize> {
for (index, popup) in self.popups.borrow().iter().enumerate() { self.popups
if popup.surface.surface.id() == *surface_id { .borrow()
return Some(index); .iter()
} .find_map(|(key, popup)| (popup.surface.surface.id() == *surface_id).then_some(key))
}
None
} }
pub fn find_popup_index_by_fractional_scale_id( pub fn find_popup_key_by_fractional_scale_id(
&self, &self,
fractional_scale_id: &ObjectId, fractional_scale_id: &ObjectId,
) -> Option<usize> { ) -> Option<usize> {
for (index, popup) in self.popups.borrow().iter().enumerate() { self.popups.borrow().iter().find_map(|(key, popup)| {
if let Some(ref fs) = popup.surface.fractional_scale { popup
if fs.id() == *fractional_scale_id { .surface
return Some(index); .fractional_scale
} .as_ref()
} .filter(|fs| fs.id() == *fractional_scale_id)
} .map(|_| key)
None })
} }
pub fn get_popup_window(&self, index: usize) -> Option<Rc<PopupWindow>> { pub fn get_popup_window(&self, key: usize) -> Option<Rc<PopupWindow>> {
self.popups self.popups
.borrow() .borrow()
.get(index) .get(key)
.map(|popup| Rc::clone(&popup.window)) .map(|popup| Rc::clone(&popup.window))
} }
pub fn destroy_popup(&self, index: usize) { pub fn destroy_popup(&self, key: usize) {
let mut popups = self.popups.borrow_mut(); if let Some(popup) = self.popups.borrow_mut().try_remove(key) {
if index < popups.len() { info!("Destroying popup with key {key}");
info!("Destroying popup at index {index}"); popup.surface.destroy();
popups.remove(index);
} }
} }
pub fn find_popup_index_by_xdg_popup_id(&self, xdg_popup_id: &ObjectId) -> Option<usize> { pub fn find_popup_key_by_xdg_popup_id(&self, xdg_popup_id: &ObjectId) -> Option<usize> {
for (index, popup) in self.popups.borrow().iter().enumerate() { self.popups
if popup.surface.xdg_popup.id() == *xdg_popup_id { .borrow()
return Some(index); .iter()
} .find_map(|(key, popup)| (popup.surface.xdg_popup.id() == *xdg_popup_id).then_some(key))
}
None
} }
} }

View file

@ -133,4 +133,11 @@ impl PopupSurface {
info!("Grabbing popup with serial {serial}"); info!("Grabbing popup with serial {serial}");
self.xdg_popup.grab(seat, serial); self.xdg_popup.grab(seat, serial);
} }
pub fn destroy(&self) {
info!("Destroying popup surface");
self.xdg_popup.destroy();
self.xdg_surface.destroy();
self.surface.destroy();
}
} }

View file

@ -6,7 +6,7 @@ use slint::{
}; };
use slint_interpreter::ComponentDefinition; use slint_interpreter::ComponentDefinition;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
use wayland_client::{protocol::{wl_output::WlOutput, wl_pointer::WlPointer, wl_surface::WlSurface}, Connection}; use wayland_client::{protocol::{wl_pointer::WlPointer, wl_surface::WlSurface}, Connection};
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport; use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport;
use crate::errors::{LayerShikaError, Result}; use crate::errors::{LayerShikaError, Result};
@ -32,7 +32,6 @@ pub struct WindowStateBuilder {
pub size: Option<PhysicalSize>, pub size: Option<PhysicalSize>,
pub output_size: Option<PhysicalSize>, pub output_size: Option<PhysicalSize>,
pub pointer: Option<Rc<WlPointer>>, pub pointer: Option<Rc<WlPointer>>,
pub output: Option<Rc<WlOutput>>,
pub window: Option<Rc<FemtoVGWindow>>, pub window: Option<Rc<FemtoVGWindow>>,
pub connection: Option<Rc<Connection>>, pub connection: Option<Rc<Connection>>,
pub scale_factor: f32, pub scale_factor: f32,
@ -76,12 +75,6 @@ impl WindowStateBuilder {
self self
} }
#[must_use]
pub fn with_output(mut self, output: Rc<WlOutput>) -> Self {
self.output = Some(output);
self
}
#[must_use] #[must_use]
pub fn with_window(mut self, window: Rc<FemtoVGWindow>) -> Self { pub fn with_window(mut self, window: Rc<FemtoVGWindow>) -> Self {
self.window = Some(window); self.window = Some(window);
@ -131,13 +124,13 @@ impl WindowStateBuilder {
} }
pub fn build(self) -> Result<(WindowState, Rc<CustomSlintPlatform>)> { pub fn build(self) -> Result<(WindowState, Rc<CustomSlintPlatform>)> {
let platform = let platform = CustomSlintPlatform::new(self.window.as_ref().ok_or_else(|| {
Rc::new(CustomSlintPlatform::new(self.window.as_ref().ok_or_else( LayerShikaError::InvalidInput {
|| LayerShikaError::InvalidInput("Window is required".into()), message: "Window is required".into(),
)?)); }
set_platform(Box::new(PlatformWrapper(Rc::clone(&platform)))).map_err(|e| { })?);
LayerShikaError::PlatformSetup(format!("Failed to set platform: {e:?}")) set_platform(Box::new(PlatformWrapper(Rc::clone(&platform))))
})?; .map_err(|e| LayerShikaError::PlatformSetup { source: e })?;
let state = WindowState::new(self)?; let state = WindowState::new(self)?;
Ok((state, platform)) Ok((state, platform))
@ -155,7 +148,6 @@ impl Default for WindowStateBuilder {
size: None, size: None,
output_size: None, output_size: None,
pointer: None, pointer: None,
output: None,
window: None, window: None,
connection: None, connection: None,
scale_factor: 1.0, scale_factor: 1.0,

View file

@ -1,4 +1,5 @@
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell;
use super::surface_builder::WindowStateBuilder; use super::surface_builder::WindowStateBuilder;
use super::dimensions::SurfaceDimensionsExt; use super::dimensions::SurfaceDimensionsExt;
use super::popup_manager::PopupManager; use super::popup_manager::PopupManager;
@ -8,16 +9,45 @@ use crate::wayland::managed_proxies::{
}; };
use crate::rendering::femtovg::main_window::FemtoVGWindow; use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::errors::{LayerShikaError, Result}; use crate::errors::{LayerShikaError, Result};
use core::result::Result as CoreResult;
use layer_shika_domain::errors::DomainError;
use layer_shika_domain::ports::windowing::RuntimeStatePort;
use layer_shika_domain::surface_dimensions::SurfaceDimensions; use layer_shika_domain::surface_dimensions::SurfaceDimensions;
use log::info; use log::info;
use slint::{LogicalPosition, PhysicalSize, ComponentHandle}; use slint::{LogicalPosition, PhysicalSize, ComponentHandle};
use slint::platform::{WindowAdapter, WindowEvent}; use slint::platform::{WindowAdapter, WindowEvent};
use slint_interpreter::ComponentInstance; use slint_interpreter::ComponentInstance;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
use wayland_client::{protocol::{wl_output::WlOutput, wl_surface::WlSurface}, Proxy}; use wayland_client::{protocol::wl_surface::WlSurface, Proxy};
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
#[derive(Debug)] pub struct SharedPointerSerial {
serial: RefCell<u32>,
}
impl Default for SharedPointerSerial {
fn default() -> Self {
Self::new()
}
}
impl SharedPointerSerial {
pub const fn new() -> Self {
Self {
serial: RefCell::new(0),
}
}
pub fn update(&self, serial: u32) {
*self.serial.borrow_mut() = serial;
}
pub fn get(&self) -> u32 {
*self.serial.borrow()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ScalingMode { enum ScalingMode {
FractionalWithViewport, FractionalWithViewport,
FractionalOnly, FractionalOnly,
@ -38,14 +68,13 @@ pub struct WindowState {
surface: ManagedWlSurface, surface: ManagedWlSurface,
#[allow(dead_code)] #[allow(dead_code)]
pointer: ManagedWlPointer, pointer: ManagedWlPointer,
#[allow(dead_code)]
output: Rc<WlOutput>,
size: PhysicalSize, size: PhysicalSize,
logical_size: PhysicalSize, logical_size: PhysicalSize,
output_size: PhysicalSize, output_size: PhysicalSize,
window: Rc<FemtoVGWindow>, window: Rc<FemtoVGWindow>,
current_pointer_position: LogicalPosition, current_pointer_position: LogicalPosition,
last_pointer_serial: u32, last_pointer_serial: u32,
shared_pointer_serial: Option<Rc<SharedPointerSerial>>,
scale_factor: f32, scale_factor: f32,
height: u32, height: u32,
exclusive_zone: i32, exclusive_zone: i32,
@ -55,34 +84,48 @@ pub struct WindowState {
impl WindowState { impl WindowState {
pub fn new(builder: WindowStateBuilder) -> Result<Self> { pub fn new(builder: WindowStateBuilder) -> Result<Self> {
let component_definition = builder.component_definition.ok_or_else(|| { let component_definition =
LayerShikaError::InvalidInput("Component definition is required".into()) builder
})?; .component_definition
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Component definition is required".into(),
})?;
let window = builder let window = builder
.window .window
.ok_or_else(|| LayerShikaError::InvalidInput("Window is required".into()))?; .ok_or_else(|| LayerShikaError::InvalidInput {
message: "Window is required".into(),
})?;
let component_instance = component_definition let component_instance = component_definition
.create() .create()
.map_err(|e| LayerShikaError::SlintComponentCreation(e.to_string()))?; .map_err(|e| LayerShikaError::SlintComponentCreation { source: e })?;
component_instance component_instance
.show() .show()
.map_err(|e| LayerShikaError::SlintComponentCreation(e.to_string()))?; .map_err(|e| LayerShikaError::SlintComponentCreation { source: e })?;
window.request_redraw(); window.request_redraw();
let connection = builder let connection = builder
.connection .connection
.ok_or_else(|| LayerShikaError::InvalidInput("Connection is required".into()))?; .ok_or_else(|| LayerShikaError::InvalidInput {
message: "Connection is required".into(),
})?;
let surface_rc = builder let surface_rc = builder
.surface .surface
.ok_or_else(|| LayerShikaError::InvalidInput("Surface is required".into()))?; .ok_or_else(|| LayerShikaError::InvalidInput {
let layer_surface_rc = builder message: "Surface is required".into(),
.layer_surface })?;
.ok_or_else(|| LayerShikaError::InvalidInput("Layer surface is required".into()))?; let layer_surface_rc =
builder
.layer_surface
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Layer surface is required".into(),
})?;
let pointer_rc = builder let pointer_rc = builder
.pointer .pointer
.ok_or_else(|| LayerShikaError::InvalidInput("Pointer is required".into()))?; .ok_or_else(|| LayerShikaError::InvalidInput {
message: "Pointer is required".into(),
})?;
let viewport = builder let viewport = builder
.viewport .viewport
@ -102,15 +145,13 @@ impl WindowState {
layer_surface, layer_surface,
surface, surface,
pointer, pointer,
output: builder
.output
.ok_or_else(|| LayerShikaError::InvalidInput("Output is required".into()))?,
size: builder.size.unwrap_or_default(), size: builder.size.unwrap_or_default(),
logical_size: PhysicalSize::default(), logical_size: PhysicalSize::default(),
output_size: builder.output_size.unwrap_or_default(), output_size: builder.output_size.unwrap_or_default(),
window, window,
current_pointer_position: LogicalPosition::default(), current_pointer_position: LogicalPosition::default(),
last_pointer_serial: 0, last_pointer_serial: 0,
shared_pointer_serial: None,
scale_factor: builder.scale_factor, scale_factor: builder.scale_factor,
height: builder.height, height: builder.height,
exclusive_zone: builder.exclusive_zone, exclusive_zone: builder.exclusive_zone,
@ -130,7 +171,7 @@ impl WindowState {
} }
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_precision_loss)]
fn configure_slint_window(&self, dimensions: &SurfaceDimensions, mode: &ScalingMode) { fn configure_slint_window(&self, dimensions: &SurfaceDimensions, mode: ScalingMode) {
match mode { match mode {
ScalingMode::FractionalWithViewport => { ScalingMode::FractionalWithViewport => {
self.window self.window
@ -157,7 +198,7 @@ impl WindowState {
} }
#[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_possible_wrap)]
fn configure_wayland_surface(&self, dimensions: &SurfaceDimensions, mode: &ScalingMode) { fn configure_wayland_surface(&self, dimensions: &SurfaceDimensions, mode: ScalingMode) {
match mode { match mode {
ScalingMode::FractionalWithViewport => { ScalingMode::FractionalWithViewport => {
self.surface.set_buffer_scale(1); self.surface.set_buffer_scale(1);
@ -199,8 +240,8 @@ impl WindowState {
scaling_mode scaling_mode
); );
self.configure_slint_window(&dimensions, &scaling_mode); self.configure_slint_window(&dimensions, scaling_mode);
self.configure_wayland_surface(&dimensions, &scaling_mode); self.configure_wayland_surface(&dimensions, scaling_mode);
info!("Window physical size: {:?}", self.window.size()); info!("Window physical size: {:?}", self.window.size());
@ -289,8 +330,15 @@ impl WindowState {
self.last_pointer_serial self.last_pointer_serial
} }
pub const fn set_last_pointer_serial(&mut self, serial: u32) { pub fn set_last_pointer_serial(&mut self, serial: u32) {
self.last_pointer_serial = serial; self.last_pointer_serial = serial;
if let Some(ref shared_serial) = self.shared_pointer_serial {
shared_serial.update(serial);
}
}
pub fn set_shared_pointer_serial(&mut self, shared_serial: Rc<SharedPointerSerial>) {
self.shared_pointer_serial = Some(shared_serial);
} }
pub fn set_popup_manager(&mut self, popup_manager: Rc<PopupManager>) { pub fn set_popup_manager(&mut self, popup_manager: Rc<PopupManager>) {
@ -306,8 +354,8 @@ impl WindowState {
} }
if let Some(popup_manager) = &self.popup_manager { if let Some(popup_manager) = &self.popup_manager {
if let Some(popup_index) = popup_manager.find_popup_index_by_surface_id(&surface_id) { if let Some(popup_key) = popup_manager.find_popup_key_by_surface_id(&surface_id) {
self.active_window = Some(ActiveWindow::Popup(popup_index)); self.active_window = Some(ActiveWindow::Popup(popup_key));
return; return;
} }
} }
@ -347,10 +395,10 @@ impl WindowState {
} }
if let Some(popup_manager) = &self.popup_manager { if let Some(popup_manager) = &self.popup_manager {
if let Some(popup_index) = if let Some(popup_key) =
popup_manager.find_popup_index_by_fractional_scale_id(&fractional_scale_id) popup_manager.find_popup_key_by_fractional_scale_id(&fractional_scale_id)
{ {
if let Some(popup_window) = popup_manager.get_popup_window(popup_index) { if let Some(popup_window) = popup_manager.get_popup_window(popup_key) {
let new_scale_factor = scale_120ths as f32 / 120.0; let new_scale_factor = scale_120ths as f32 / 120.0;
info!("Updating popup scale factor to {new_scale_factor} ({scale_120ths}x)"); info!("Updating popup scale factor to {new_scale_factor} ({scale_120ths}x)");
popup_window.set_scale_factor(new_scale_factor); popup_window.set_scale_factor(new_scale_factor);
@ -364,8 +412,8 @@ impl WindowState {
self.active_window = None; self.active_window = None;
} }
pub fn clear_active_window_if_popup(&mut self, popup_index: usize) { pub fn clear_active_window_if_popup(&mut self, popup_key: usize) {
if self.active_window == Some(ActiveWindow::Popup(popup_index)) { if self.active_window == Some(ActiveWindow::Popup(popup_key)) {
self.active_window = None; self.active_window = None;
} }
} }
@ -374,3 +422,11 @@ impl WindowState {
&self.popup_manager &self.popup_manager
} }
} }
impl RuntimeStatePort for WindowState {
fn render_frame_if_dirty(&self) -> CoreResult<(), DomainError> {
WindowState::render_frame_if_dirty(self).map_err(|e| DomainError::Adapter {
source: Box::new(e),
})
}
}

View file

@ -1,7 +1,7 @@
use crate::system::WindowingSystem;
use crate::Result; use crate::Result;
use crate::system::WindowingSystem;
use layer_shika_adapters::platform::slint_interpreter::ComponentDefinition; use layer_shika_adapters::platform::slint_interpreter::ComponentDefinition;
use layer_shika_domain::config::{AnchorEdges, Layer, Margins, WindowConfig}; use layer_shika_domain::prelude::{AnchorEdges, Layer, Margins, WindowConfig};
pub struct NeedsComponent; pub struct NeedsComponent;
pub struct HasComponent { pub struct HasComponent {

View file

@ -8,8 +8,10 @@ use layer_shika_domain::errors::DomainError;
use std::result::Result as StdResult; use std::result::Result as StdResult;
pub use builder::LayerShika; pub use builder::LayerShika;
pub use layer_shika_adapters::PopupWindow;
pub use layer_shika_adapters::close_current_popup;
pub use layer_shika_adapters::platform::{calloop, slint, slint_interpreter}; pub use layer_shika_adapters::platform::{calloop, slint, slint_interpreter};
pub use layer_shika_domain::config::AnchorEdges; pub use layer_shika_domain::value_objects::anchor::AnchorEdges;
pub type Result<T> = StdResult<T, Error>; pub type Result<T> = StdResult<T, Error>;
@ -20,4 +22,10 @@ pub enum Error {
#[error("Domain error: {0}")] #[error("Domain error: {0}")]
Domain(#[from] DomainError), Domain(#[from] DomainError),
#[error("WindowingSystem has been dropped")]
SystemDropped,
#[error("Cannot run while EventLoopHandle exists")]
EventLoopHandleExists,
} }

View file

@ -1,18 +1,18 @@
use crate::Result; use crate::{Error, Result};
use layer_shika_adapters::platform::calloop::{EventSource, InsertError, RegistrationToken}; use layer_shika_adapters::errors::EventLoopError;
use layer_shika_adapters::platform::calloop::{EventSource, RegistrationToken};
use layer_shika_adapters::platform::slint_interpreter::{ComponentDefinition, ComponentInstance}; use layer_shika_adapters::platform::slint_interpreter::{ComponentDefinition, ComponentInstance};
use layer_shika_adapters::wayland::{ use layer_shika_adapters::wayland::{
config::WaylandWindowConfig, config::WaylandWindowConfig, shell_adapter::WaylandWindowingSystem,
shell_adapter::WaylandWindowingSystem, surfaces::surface_state::WindowState,
};
use layer_shika_adapters::event_loop::calloop_adapter::{
EventLoopAdapter, RuntimeStateAdapter, SystemAdapter,
}; };
use layer_shika_domain::config::WindowConfig; use layer_shika_domain::config::WindowConfig;
use std::cell::{Ref, RefCell};
use std::rc::{Rc, Weak};
use std::result::Result as StdResult; use std::result::Result as StdResult;
pub struct EventLoopHandle { pub struct EventLoopHandle {
adapter: EventLoopAdapter, system: Weak<RefCell<WaylandWindowingSystem>>,
} }
impl EventLoopHandle { impl EventLoopHandle {
@ -20,36 +20,47 @@ impl EventLoopHandle {
&self, &self,
source: S, source: S,
mut callback: F, mut callback: F,
) -> StdResult<RegistrationToken, InsertError<S>> ) -> StdResult<RegistrationToken, Error>
where where
S: EventSource<Ret = R> + 'static, S: EventSource<Ret = R> + 'static,
F: FnMut(S::Event, &mut S::Metadata, &mut RuntimeState) -> R + 'static, F: FnMut(S::Event, &mut S::Metadata, RuntimeState<'_>) -> R + 'static,
{ {
self.adapter let system = self.system.upgrade().ok_or(Error::SystemDropped)?;
.insert_source_with_adapter(source, move |event, metadata, adapter| { let loop_handle = system.borrow().event_loop_handle();
let mut runtime_state = RuntimeState { adapter };
callback(event, metadata, &mut runtime_state) loop_handle
.insert_source(source, move |event, metadata, window_state| {
let runtime_state = RuntimeState { window_state };
callback(event, metadata, runtime_state)
})
.map_err(|e| {
Error::Adapter(
EventLoopError::InsertSource {
message: format!("{e:?}"),
}
.into(),
)
}) })
} }
} }
pub struct RuntimeState { pub struct RuntimeState<'a> {
adapter: RuntimeStateAdapter, window_state: &'a mut WindowState,
} }
impl RuntimeState { impl RuntimeState<'_> {
#[must_use] #[must_use]
pub fn component_instance(&self) -> &ComponentInstance { pub fn component_instance(&self) -> &ComponentInstance {
self.adapter.component_instance() self.window_state.component_instance()
} }
pub fn render_frame_if_dirty(&self) -> Result<()> { pub fn render_frame_if_dirty(&mut self) -> Result<()> {
Ok(self.adapter.render_frame_if_dirty()?) Ok(self.window_state.render_frame_if_dirty()?)
} }
} }
pub struct WindowingSystem { pub struct WindowingSystem {
adapter: SystemAdapter, inner: Rc<RefCell<WaylandWindowingSystem>>,
} }
impl WindowingSystem { impl WindowingSystem {
@ -58,26 +69,27 @@ impl WindowingSystem {
config: WindowConfig, config: WindowConfig,
) -> Result<Self> { ) -> Result<Self> {
let wayland_config = WaylandWindowConfig::from_domain_config(component_definition, config); let wayland_config = WaylandWindowConfig::from_domain_config(component_definition, config);
let inner_system = WaylandWindowingSystem::new(wayland_config)?; let inner = WaylandWindowingSystem::new(wayland_config)?;
let adapter = SystemAdapter::new(inner_system);
Ok(Self { adapter }) Ok(Self {
inner: Rc::new(RefCell::new(inner)),
})
} }
#[must_use] #[must_use]
pub fn event_loop_handle(&self) -> EventLoopHandle { pub fn event_loop_handle(&self) -> EventLoopHandle {
EventLoopHandle { EventLoopHandle {
adapter: self.adapter.event_loop_handle(), system: Rc::downgrade(&self.inner),
} }
} }
pub fn run(&mut self) -> Result<()> { pub fn run(&mut self) -> Result<()> {
self.adapter.run()?; self.inner.borrow_mut().run()?;
Ok(()) Ok(())
} }
#[must_use] #[must_use]
pub const fn component_instance(&self) -> &ComponentInstance { pub fn component_instance(&self) -> Ref<'_, ComponentInstance> {
self.adapter.component_instance() Ref::map(self.inner.borrow(), |system| system.component_instance())
} }
} }

View file

@ -1,10 +1,6 @@
#![allow(clippy::pub_use)] use crate::value_objects::anchor::AnchorEdges;
use crate::value_objects::layer::Layer;
pub use crate::entities::component::UiComponentHandle; use crate::value_objects::margins::Margins;
pub use crate::value_objects::anchor::AnchorEdges;
pub use crate::value_objects::dimensions::WindowHeight;
pub use crate::value_objects::layer::Layer;
pub use crate::value_objects::margins::Margins;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct WindowConfig { pub struct WindowConfig {

View file

@ -1,19 +1,26 @@
use std::error::Error;
use std::result::Result as StdResult; use std::result::Result as StdResult;
use thiserror::Error; use thiserror::Error as ThisError;
pub type Result<T> = StdResult<T, DomainError>; pub type Result<T> = StdResult<T, DomainError>;
#[derive(Error, Debug)] #[derive(ThisError, Debug)]
pub enum DomainError { pub enum DomainError {
#[error("Configuration error: {0}")] #[error("invalid configuration: {message}")]
Configuration(String), Configuration { message: String },
#[error("Invalid dimensions: {0}")] #[error("invalid dimensions {width}x{height}")]
InvalidDimensions(String), InvalidDimensions { width: u32, height: u32 },
#[error("Invalid input: {0}")] #[error("invalid input: {message}")]
InvalidInput(String), InvalidInput { message: String },
#[error("Calculation error: {0}")] #[error("calculation error: {operation} failed - {reason}")]
Calculation(String), Calculation { operation: String, reason: String },
#[error("adapter error")]
Adapter {
#[source]
source: Box<dyn Error + Send + Sync>,
},
} }

View file

@ -1 +1 @@
pub mod window_events;

View file

@ -1,15 +0,0 @@
#[derive(Debug, Clone)]
pub enum WindowEvent {
Resized { width: u32, height: u32 },
ScaleChanged { scale: f32 },
CloseRequested,
Focused,
Unfocused,
CursorMoved { x: f64, y: f64 },
CursorEntered,
CursorLeft,
MouseButtonPressed { button: u32 },
MouseButtonReleased { button: u32 },
KeyPressed { key: u32 },
KeyReleased { key: u32 },
}

View file

@ -1,6 +1,7 @@
pub mod config; pub mod config;
pub mod entities; pub mod entities;
pub mod errors; pub mod errors;
pub mod events; pub mod ports;
pub mod prelude;
pub mod surface_dimensions; pub mod surface_dimensions;
pub mod value_objects; pub mod value_objects;

1
domain/src/ports/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod windowing;

View file

@ -0,0 +1,9 @@
use crate::errors::DomainError;
pub trait WindowingSystemPort {
fn run(&mut self) -> Result<(), DomainError>;
}
pub trait RuntimeStatePort {
fn render_frame_if_dirty(&self) -> Result<(), DomainError>;
}

12
domain/src/prelude.rs Normal file
View file

@ -0,0 +1,12 @@
#![allow(clippy::pub_use)]
pub use crate::config::WindowConfig;
pub use crate::entities::component::UiComponentHandle;
pub use crate::entities::surface::SurfaceHandle;
pub use crate::entities::window::WindowHandle;
pub use crate::errors::{DomainError, Result};
pub use crate::surface_dimensions::SurfaceDimensions;
pub use crate::value_objects::anchor::AnchorEdges;
pub use crate::value_objects::dimensions::WindowHeight;
pub use crate::value_objects::layer::Layer;
pub use crate::value_objects::margins::Margins;