refactor: migrate to reworked architecture

This commit is contained in:
drendog 2025-10-28 06:18:37 +01:00
parent e6579e1a35
commit 6353a786af
Signed by: dwenya
GPG key ID: 8DD77074645332D0
59 changed files with 861 additions and 290 deletions

17
Cargo.lock generated
View file

@ -1854,8 +1854,18 @@ dependencies = [
[[package]]
name = "layer-shika"
version = "0.1.0"
dependencies = [
"layer-shika-adapters",
"layer-shika-domain",
"thiserror 2.0.17",
]
[[package]]
name = "layer-shika-adapters"
version = "0.1.0"
dependencies = [
"glutin",
"layer-shika-domain",
"log",
"raw-window-handle",
"slint",
@ -1866,6 +1876,13 @@ dependencies = [
"wayland-protocols",
]
[[package]]
name = "layer-shika-domain"
version = "0.1.0"
dependencies = [
"thiserror 2.0.17",
]
[[package]]
name = "lazy-bytes-cast"
version = "5.0.1"

View file

@ -1,48 +1,31 @@
[package]
name = "layer-shika"
version = "0.1.0"
edition = "2021"
description = "A layer shell library crate with Slint UI"
license = "AGPL-3.0-or-later"
repository = "https://codeberg.org/waydeer/layer-shika"
readme = "README.md"
keywords = ["layer-shell", "wayland", "slint", "femtovg", "smithay"]
categories = ["gui"]
[workspace]
resolver = "2"
members = ["domain", "adapters", "composition"]
[lints.clippy]
all = { level = "warn", priority = -1 }
cargo = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
clone_on_ref_ptr = "warn"
multiple-crate-versions = "allow"
module_name_repetitions = "allow"
unwrap_used = "warn"
[workspace.lints.clippy]
all = { level = "deny", priority = -1 }
pedantic = { level = "deny", priority = -1 }
disallowed_types = "deny"
disallowed_methods = "deny"
pub_use = "deny"
unwrap_used = "deny"
expect_used = "deny"
print_stdout = "deny"
print_stderr = "deny"
panic = "deny"
indexing_slicing = "deny"
exit = "deny"
redundant_clone = "deny"
clone_on_ref_ptr = "deny"
vec_box = "deny"
large_enum_variant = "deny"
await_holding_lock = "deny"
future_not_send = "deny"
cognitive_complexity = "deny"
needless_collect = "deny"
let_underscore_must_use = "deny"
absolute_paths = "deny"
[dependencies]
glutin = { version = "0.32.3", default-features = false, features = [
"wayland",
] }
log = "0.4.28"
raw-window-handle = "0.6.2"
slint = { version = "1.14.1", default-features = false, features = [
"compat-1-2",
"renderer-femtovg",
"backend-winit-wayland",
] }
# slint = { path = "../slint/api/rs/slint", default-features = false, features = [
# "compat-1-2",
# "renderer-femtovg",
# "backend-winit-wayland",
# ] }
slint-interpreter = { version = "1.14.1", default-features = false, features = [
"compat-1-2",
] }
# slint-interpreter = { path = "../slint/internal/interpreter", default-features = false, features = [
# "compat-1-2",
# ] }
smithay-client-toolkit = "0.20.0"
thiserror = "2.0.17"
wayland-client = "0.31.11"
wayland-protocols = { version = "0.32.9", features = ["client", "staging"] }
missing_errors_doc = "allow"
missing_panics_doc = "allow"
must_use_candidate = "allow"
uninlined_format_args = "allow"

29
adapters/Cargo.toml Normal file
View file

@ -0,0 +1,29 @@
[package]
name = "layer-shika-adapters"
version = "0.1.0"
edition = "2021"
description = "Adapters layer for layer-shika"
license = "AGPL-3.0-or-later"
[lints]
workspace = true
[dependencies]
glutin = { version = "0.32.3", default-features = false, features = [
"wayland",
] }
layer-shika-domain = { version = "0.1.0", path = "../domain" }
log = "0.4.28"
raw-window-handle = "0.6.2"
slint = { version = "1.14.1", default-features = false, features = [
"compat-1-2",
"renderer-femtovg",
"backend-winit-wayland",
] }
slint-interpreter = { version = "1.14.1", default-features = false, features = [
"compat-1-2",
] }
smithay-client-toolkit = "0.20.0"
thiserror = "2.0.17"
wayland-client = "0.31.11"
wayland-protocols = { version = "0.32.9", features = ["client", "staging"] }

View file

@ -1,3 +1,4 @@
use layer_shika_domain::errors::DomainError;
use std::result::Result as StdResult;
use thiserror::Error;
use wayland_client::backend::WaylandError;
@ -6,6 +7,9 @@ pub type Result<T> = StdResult<T, LayerShikaError>;
#[derive(Error, Debug)]
pub enum LayerShikaError {
#[error("Domain error: {0}")]
Domain(#[from] DomainError),
#[error("Failed to connect to Wayland: {0}")]
WaylandConnection(#[from] wayland_client::ConnectError),

View file

@ -0,0 +1,79 @@
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

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

18
adapters/src/lib.rs Normal file
View file

@ -0,0 +1,18 @@
#![allow(clippy::pub_use)]
pub mod errors;
pub mod event_loop;
pub mod rendering;
pub mod wayland;
pub mod platform {
pub use slint;
pub use slint_interpreter;
pub mod calloop {
pub use smithay_client_toolkit::reexports::calloop::channel;
pub use smithay_client_toolkit::reexports::calloop::{
EventSource, InsertError, PostAction, RegistrationToken,
};
}
}

View file

@ -40,31 +40,37 @@ pub struct EGLContextBuilder {
}
impl EGLContextBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_display_id(mut self, display_id: ObjectId) -> Self {
self.display_id = Some(display_id);
self
}
#[must_use]
pub fn with_surface_id(mut self, surface_id: ObjectId) -> Self {
self.surface_id = Some(surface_id);
self
}
#[must_use]
pub const fn with_size(mut self, size: PhysicalSize) -> Self {
self.size = Some(size);
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,
@ -110,6 +116,7 @@ impl EGLContextBuilder {
}
impl EGLContext {
#[must_use]
pub fn builder() -> EGLContextBuilder {
EGLContextBuilder::new()
}

View file

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

View file

@ -22,6 +22,7 @@ pub struct FemtoVGWindow {
}
impl FemtoVGWindow {
#[must_use]
pub fn new(renderer: FemtoVGRenderer) -> Rc<Self> {
Rc::new_cyclic(|weak_self| {
let window = Window::new(Weak::clone(weak_self) as Weak<dyn WindowAdapter>);

View file

@ -0,0 +1,2 @@
pub mod main_window;
pub mod popup_window;

View file

@ -8,7 +8,7 @@ use slint::{
use std::cell::Cell;
use std::rc::{Rc, Weak};
use super::femtovg_window::RenderState;
use super::main_window::RenderState;
#[allow(dead_code)]
pub struct PopupWindow {
@ -21,6 +21,7 @@ pub struct PopupWindow {
#[allow(dead_code)]
impl PopupWindow {
#[must_use]
pub fn new(renderer: FemtoVGRenderer) -> Rc<Self> {
Rc::new_cyclic(|weak_self| {
let window = Window::new(Weak::clone(weak_self) as Weak<dyn WindowAdapter>);

View file

@ -0,0 +1,3 @@
pub mod egl;
pub mod femtovg;
pub mod slint_integration;

View file

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

View file

@ -5,7 +5,7 @@ use slint::{
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use super::femtovg_window::FemtoVGWindow;
use crate::rendering::femtovg::main_window::FemtoVGWindow;
type PopupCreator = dyn Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError>;
@ -16,6 +16,7 @@ pub struct CustomSlintPlatform {
}
impl CustomSlintPlatform {
#[must_use]
pub fn new(window: &Rc<FemtoVGWindow>) -> Self {
Self {
main_window: Rc::downgrade(window),

View file

@ -0,0 +1,76 @@
use layer_shika_domain::config::{AnchorEdges, Layer, Margins, WindowConfig as DomainWindowConfig};
use slint_interpreter::ComponentDefinition;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::{self},
zwlr_layer_surface_v1::{Anchor, KeyboardInteractivity},
};
#[derive(Debug, Clone, Copy)]
pub(crate) struct LayerSurfaceParams {
pub anchor: Anchor,
pub margin: Margins,
pub exclusive_zone: i32,
pub keyboard_interactivity: KeyboardInteractivity,
pub height: u32,
}
#[derive(Clone)]
pub struct WaylandWindowConfig {
pub height: u32,
pub layer: zwlr_layer_shell_v1::Layer,
pub margin: Margins,
pub anchor: Anchor,
pub keyboard_interactivity: KeyboardInteractivity,
pub exclusive_zone: i32,
pub scale_factor: f32,
pub namespace: String,
pub component_definition: ComponentDefinition,
}
impl WaylandWindowConfig {
#[must_use]
pub fn from_domain_config(
component_definition: ComponentDefinition,
domain_config: DomainWindowConfig,
) -> Self {
Self {
height: domain_config.height,
layer: convert_layer(domain_config.layer),
margin: domain_config.margin,
anchor: convert_anchor(domain_config.anchor),
keyboard_interactivity: KeyboardInteractivity::OnDemand,
exclusive_zone: domain_config.exclusive_zone,
scale_factor: domain_config.scale_factor,
namespace: domain_config.namespace,
component_definition,
}
}
}
const fn convert_layer(layer: Layer) -> zwlr_layer_shell_v1::Layer {
match layer {
Layer::Background => zwlr_layer_shell_v1::Layer::Background,
Layer::Bottom => zwlr_layer_shell_v1::Layer::Bottom,
Layer::Top => zwlr_layer_shell_v1::Layer::Top,
Layer::Overlay => zwlr_layer_shell_v1::Layer::Overlay,
}
}
const fn convert_anchor(anchor: AnchorEdges) -> Anchor {
let mut result = Anchor::empty();
if anchor.has_top() {
result = result.union(Anchor::Top);
}
if anchor.has_bottom() {
result = result.union(Anchor::Bottom);
}
if anchor.has_left() {
result = result.union(Anchor::Left);
}
if anchor.has_right() {
result = result.union(Anchor::Right);
}
result
}

View file

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

View file

@ -1,4 +1,4 @@
use super::WindowState;
use crate::wayland::surfaces::surface_state::WindowState;
use crate::impl_empty_dispatch;
use log::info;
use slint::{
@ -271,14 +271,14 @@ impl Dispatch<XdgPopup, ()> for WindowState {
info!("XdgPopup dismissed by compositor");
let popup_id = xdg_popup.id();
let popup_index = state
.popup_manager
.popup_manager()
.as_ref()
.and_then(|pm| pm.find_popup_index_by_xdg_popup_id(&popup_id));
if let Some(index) = popup_index {
info!("Destroying popup at index {index}");
state.clear_active_window_if_popup(index);
if let Some(popup_manager) = &state.popup_manager {
if let Some(popup_manager) = &state.popup_manager() {
popup_manager.destroy_popup(index);
}
}
@ -304,7 +304,7 @@ impl Dispatch<XdgSurface, ()> for WindowState {
info!("XdgSurface Configure received, sending ack with serial {serial}");
xdg_surface.ack_configure(serial);
if let Some(popup_manager) = &state.popup_manager {
if let Some(popup_manager) = &state.popup_manager() {
info!("Marking all popups as dirty after Configure");
popup_manager.mark_all_popups_dirty();
}

View file

@ -0,0 +1,2 @@
pub mod event_dispatcher;
pub mod event_macros;

View file

@ -10,20 +10,19 @@ use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_man
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase;
use super::state::WindowState;
use crate::wayland::surfaces::surface_state::WindowState;
pub struct GlobalCtx {
pub struct GlobalContext {
pub compositor: WlCompositor,
pub output: WlOutput,
pub layer_shell: ZwlrLayerShellV1,
pub seat: WlSeat,
#[allow(dead_code)]
pub xdg_wm_base: Option<XdgWmBase>,
pub fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
pub viewporter: Option<WpViewporter>,
}
impl GlobalCtx {
impl GlobalContext {
pub fn initialize(
connection: &Connection,
queue_handle: &QueueHandle<WindowState>,

View file

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

View file

@ -13,6 +13,7 @@ pub struct ManagedWlPointer {
}
impl ManagedWlPointer {
#[must_use]
pub const fn new(pointer: Rc<WlPointer>, connection: Rc<Connection>) -> Self {
Self {
pointer,
@ -50,6 +51,7 @@ pub struct ManagedWlSurface {
}
impl ManagedWlSurface {
#[must_use]
pub const fn new(surface: Rc<WlSurface>, connection: Rc<Connection>) -> Self {
Self {
surface,
@ -86,6 +88,7 @@ pub struct ManagedZwlrLayerSurfaceV1 {
}
impl ManagedZwlrLayerSurfaceV1 {
#[must_use]
pub const fn new(layer_surface: Rc<ZwlrLayerSurfaceV1>, connection: Rc<Connection>) -> Self {
Self {
layer_surface,
@ -122,6 +125,7 @@ pub struct ManagedWpFractionalScaleV1 {
}
impl ManagedWpFractionalScaleV1 {
#[must_use]
pub const fn new(
fractional_scale: Rc<WpFractionalScaleV1>,
connection: Rc<Connection>,
@ -162,6 +166,7 @@ pub struct ManagedWpViewport {
}
impl ManagedWpViewport {
#[must_use]
pub const fn new(viewport: Rc<WpViewport>, connection: Rc<Connection>) -> Self {
Self {
viewport,

View file

@ -0,0 +1,7 @@
pub mod config;
pub mod connection;
pub mod event_handling;
pub mod globals;
pub mod managed_proxies;
pub mod shell_adapter;
pub mod surfaces;

View file

@ -1,14 +1,14 @@
use super::{
config::{LayerSurfaceParams, WindowConfig},
globals::GlobalCtx,
popup_manager::{PopupContext, PopupManager},
state::{builder::WindowStateBuilder, WindowState},
surface::{SurfaceCtx, SurfaceSetupParams},
use crate::wayland::{
config::{LayerSurfaceParams, WaylandWindowConfig},
globals::context::GlobalContext,
surfaces::popup_manager::{PopupContext, PopupManager},
surfaces::{surface_builder::WindowStateBuilder, surface_state::WindowState},
surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams},
};
use crate::{
errors::{LayerShikaError, Result},
rendering::{
egl_context::EGLContext, femtovg_window::FemtoVGWindow, slint_platform::CustomSlintPlatform,
egl::context::EGLContext, femtovg::main_window::FemtoVGWindow, slint_integration::platform::CustomSlintPlatform,
},
};
use log::{error, info};
@ -26,7 +26,7 @@ use wayland_client::{
Connection, EventQueue, Proxy,
};
pub struct WindowingSystem {
pub struct WaylandWindowingSystem {
state: WindowState,
connection: Rc<Connection>,
event_queue: EventQueue<WindowState>,
@ -34,8 +34,8 @@ pub struct WindowingSystem {
popup_manager: Rc<PopupManager>,
}
impl WindowingSystem {
pub(super) fn new(config: WindowConfig) -> Result<Self> {
impl WaylandWindowingSystem {
pub fn new(config: WaylandWindowConfig) -> Result<Self> {
info!("Initializing WindowingSystem");
let (connection, event_queue) = Self::init_wayland_connection()?;
let (state, global_ctx, platform) = Self::init_state(config, &connection, &event_queue)?;
@ -79,11 +79,11 @@ impl WindowingSystem {
}
fn init_state(
config: WindowConfig,
config: WaylandWindowConfig,
connection: &Connection,
event_queue: &EventQueue<WindowState>,
) -> Result<(WindowState, GlobalCtx, Rc<CustomSlintPlatform>)> {
let global_ctx = GlobalCtx::initialize(connection, &event_queue.handle())
) -> Result<(WindowState, GlobalContext, Rc<CustomSlintPlatform>)> {
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())
.map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?;
let layer_surface_params = LayerSurfaceParams {
@ -177,7 +177,7 @@ impl WindowingSystem {
fn initialize_renderer(
surface: &Rc<WlSurface>,
display: &WlDisplay,
config: &WindowConfig,
config: &WaylandWindowConfig,
) -> Result<Rc<FemtoVGWindow>> {
let init_size = PhysicalSize::new(1, 1);
@ -293,7 +293,8 @@ impl WindowingSystem {
self.state.component_instance()
}
pub fn window(&self) -> Rc<FemtoVGWindow> {
#[allow(dead_code)]
pub(crate) fn window(&self) -> Rc<FemtoVGWindow> {
self.state.window()
}

View file

@ -0,0 +1,17 @@
use layer_shika_domain::surface_dimensions::SurfaceDimensions;
use slint::PhysicalSize;
pub trait SurfaceDimensionsExt {
fn logical_size(&self) -> PhysicalSize;
fn physical_size(&self) -> PhysicalSize;
}
impl SurfaceDimensionsExt for SurfaceDimensions {
fn logical_size(&self) -> PhysicalSize {
PhysicalSize::new(self.logical_width, self.logical_height)
}
fn physical_size(&self) -> PhysicalSize {
PhysicalSize::new(self.physical_width, self.physical_height)
}
}

View file

@ -1,4 +1,4 @@
use super::{config::LayerSurfaceParams, state::WindowState};
use crate::wayland::{config::LayerSurfaceParams, surfaces::surface_state::WindowState};
use log::info;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::{Layer, ZwlrLayerShellV1},
@ -36,7 +36,10 @@ pub struct SurfaceCtx {
}
impl SurfaceCtx {
pub fn setup(setup_params: &SurfaceSetupParams<'_>, params: &LayerSurfaceParams) -> Self {
pub(crate) fn setup(
setup_params: &SurfaceSetupParams<'_>,
params: &LayerSurfaceParams,
) -> Self {
let surface = Rc::new(
setup_params
.compositor

View file

@ -0,0 +1,6 @@
pub mod dimensions;
pub mod layer_surface;
pub mod popup_manager;
pub mod popup_surface;
pub mod surface_builder;
pub mod surface_state;

View file

@ -1,5 +1,6 @@
use crate::errors::{LayerShikaError, Result};
use crate::rendering::{egl_context::EGLContext, popup_window::PopupWindow};
use crate::rendering::egl::context::EGLContext;
use crate::rendering::femtovg::popup_window::PopupWindow;
use log::info;
use slint::{platform::femtovg_renderer::FemtoVGRenderer, PhysicalSize, WindowSize};
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
@ -14,7 +15,8 @@ use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_man
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase;
use super::{popup::PopupSurface, state::WindowState};
use super::popup_surface::PopupSurface;
use super::surface_state::WindowState;
pub struct PopupContext {
compositor: WlCompositor,
@ -28,6 +30,7 @@ pub struct PopupContext {
}
impl PopupContext {
#[must_use]
pub const fn new(
compositor: WlCompositor,
xdg_wm_base: Option<XdgWmBase>,
@ -62,6 +65,7 @@ pub struct PopupManager {
}
impl PopupManager {
#[must_use]
pub const fn new(context: PopupContext, initial_scale_factor: f32) -> Self {
Self {
context,
@ -107,7 +111,7 @@ impl PopupManager {
info!("Popup logical size: {logical_size:?}, physical size: {popup_size:?}");
let popup_surface = PopupSurface::create(&super::popup::PopupSurfaceParams {
let popup_surface = PopupSurface::create(&super::popup_surface::PopupSurfaceParams {
compositor: &self.context.compositor,
xdg_wm_base,
parent_layer_surface,

View file

@ -20,7 +20,7 @@ use wayland_protocols::xdg::shell::client::{
xdg_wm_base::XdgWmBase,
};
use super::state::WindowState;
use super::surface_state::WindowState;
#[allow(dead_code)]
pub struct PopupSurfaceParams<'a> {

View file

@ -1,4 +1,5 @@
use std::rc::Rc;
use std::result::Result as StdResult;
use slint::{
platform::{set_platform, Platform, WindowAdapter},
PhysicalSize, PlatformError,
@ -8,15 +9,16 @@ use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::z
use wayland_client::{protocol::{wl_output::WlOutput, wl_pointer::WlPointer, wl_surface::WlSurface}, Connection};
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport;
use crate::{errors::{LayerShikaError, Result}, rendering::{femtovg_window::FemtoVGWindow, slint_platform::CustomSlintPlatform}};
use crate::errors::{LayerShikaError, Result};
use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::rendering::slint_integration::platform::CustomSlintPlatform;
use super::WindowState;
use super::surface_state::WindowState;
struct PlatformWrapper(Rc<CustomSlintPlatform>);
impl Platform for PlatformWrapper {
#[allow(clippy::absolute_paths)]
fn create_window_adapter(&self) -> std::result::Result<Rc<dyn WindowAdapter>, PlatformError> {
fn create_window_adapter(&self) -> StdResult<Rc<dyn WindowAdapter>, PlatformError> {
self.0.create_window_adapter()
}
}

View file

@ -1,5 +1,14 @@
use std::rc::Rc;
use builder::WindowStateBuilder;
use super::surface_builder::WindowStateBuilder;
use super::dimensions::SurfaceDimensionsExt;
use super::popup_manager::PopupManager;
use crate::wayland::managed_proxies::{
ManagedWlPointer, ManagedWlSurface, ManagedZwlrLayerSurfaceV1,
ManagedWpFractionalScaleV1, ManagedWpViewport,
};
use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::errors::{LayerShikaError, Result};
use layer_shika_domain::surface_dimensions::SurfaceDimensions;
use log::info;
use slint::{LogicalPosition, PhysicalSize, ComponentHandle};
use slint::platform::{WindowAdapter, WindowEvent};
@ -7,17 +16,6 @@ use slint_interpreter::ComponentInstance;
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_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
use crate::rendering::femtovg_window::FemtoVGWindow;
use crate::errors::{LayerShikaError, Result};
use crate::windowing::surface_dimensions::SurfaceDimensions;
use crate::windowing::popup_manager::PopupManager;
use crate::windowing::proxies::{
ManagedWlPointer, ManagedWlSurface, ManagedZwlrLayerSurfaceV1,
ManagedWpFractionalScaleV1, ManagedWpViewport,
};
pub mod builder;
pub mod dispatches;
#[derive(Debug)]
enum ScalingMode {
@ -233,18 +231,14 @@ impl WindowState {
&self.current_pointer_position
}
pub fn window(&self) -> Rc<FemtoVGWindow> {
pub(crate) fn window(&self) -> Rc<FemtoVGWindow> {
Rc::clone(&self.window)
}
pub fn layer_surface(&self) -> Rc<ZwlrLayerSurfaceV1> {
pub(crate) fn layer_surface(&self) -> Rc<ZwlrLayerSurfaceV1> {
Rc::clone(self.layer_surface.inner())
}
pub fn surface(&self) -> Rc<WlSurface> {
Rc::clone(self.surface.inner())
}
pub const fn height(&self) -> u32 {
self.height
}
@ -264,6 +258,10 @@ impl WindowState {
&self.component_instance
}
pub fn render_frame_if_dirty(&self) -> Result<()> {
self.window.render_frame_if_dirty()
}
#[allow(clippy::cast_precision_loss)]
pub fn update_scale_factor(&mut self, scale_120ths: u32) {
let new_scale_factor = scale_120ths as f32 / 120.0;
@ -362,13 +360,17 @@ impl WindowState {
}
}
pub(super) const fn clear_active_window(&mut self) {
pub const fn clear_active_window(&mut self) {
self.active_window = None;
}
pub(super) fn clear_active_window_if_popup(&mut self, popup_index: usize) {
pub fn clear_active_window_if_popup(&mut self, popup_index: usize) {
if self.active_window == Some(ActiveWindow::Popup(popup_index)) {
self.active_window = None;
}
}
pub const fn popup_manager(&self) -> &Option<Rc<PopupManager>> {
&self.popup_manager
}
}

18
composition/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "layer-shika"
version = "0.1.0"
edition = "2021"
description = "A layer shell library crate with Slint UI"
license = "AGPL-3.0-or-later"
repository = "https://codeberg.org/waydeer/layer-shika"
readme = "../README.md"
keywords = ["layer-shell", "wayland", "slint", "femtovg", "smithay"]
categories = ["gui"]
[lints]
workspace = true
[dependencies]
layer-shika-adapters = { path = "../adapters" }
layer-shika-domain = { path = "../domain" }
thiserror = "2.0"

View file

@ -0,0 +1,79 @@
use crate::system::WindowingSystem;
use crate::Result;
use layer_shika_adapters::platform::slint_interpreter::ComponentDefinition;
use layer_shika_domain::config::{AnchorEdges, Layer, Margins, WindowConfig};
pub struct NeedsComponent;
pub struct HasComponent {
component_definition: ComponentDefinition,
}
pub struct LayerShika<State> {
state: State,
config: WindowConfig,
}
impl LayerShika<NeedsComponent> {
#[must_use]
pub fn new(component_definition: ComponentDefinition) -> LayerShika<HasComponent> {
LayerShika {
state: HasComponent {
component_definition,
},
config: WindowConfig::default(),
}
}
}
impl LayerShika<HasComponent> {
#[must_use]
pub const fn with_height(mut self, height: u32) -> Self {
self.config.height = height;
self
}
#[must_use]
pub const fn with_layer(mut self, layer: Layer) -> Self {
self.config.layer = layer;
self
}
#[must_use]
pub const fn with_margin(mut self, top: i32, right: i32, bottom: i32, left: i32) -> Self {
self.config.margin = Margins {
top,
right,
bottom,
left,
};
self
}
#[must_use]
pub const fn with_anchor(mut self, anchor: AnchorEdges) -> Self {
self.config.anchor = anchor;
self
}
#[must_use]
pub const fn with_exclusive_zone(mut self, zone: i32) -> Self {
self.config.exclusive_zone = zone;
self
}
#[must_use]
pub fn with_namespace(mut self, namespace: String) -> Self {
self.config.namespace = namespace;
self
}
#[must_use]
pub const fn with_scale_factor(mut self, scale_factor: f32) -> Self {
self.config.scale_factor = scale_factor;
self
}
pub fn build(self) -> Result<WindowingSystem> {
WindowingSystem::new(self.state.component_definition, self.config)
}
}

23
composition/src/lib.rs Normal file
View file

@ -0,0 +1,23 @@
#![allow(clippy::pub_use)]
pub mod builder;
pub mod system;
use layer_shika_adapters::errors::LayerShikaError;
use layer_shika_domain::errors::DomainError;
use std::result::Result as StdResult;
pub use builder::LayerShika;
pub use layer_shika_adapters::platform::{calloop, slint, slint_interpreter};
pub use layer_shika_domain::config::AnchorEdges;
pub type Result<T> = StdResult<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Adapter error: {0}")]
Adapter(#[from] LayerShikaError),
#[error("Domain error: {0}")]
Domain(#[from] DomainError),
}

83
composition/src/system.rs Normal file
View file

@ -0,0 +1,83 @@
use crate::Result;
use layer_shika_adapters::platform::calloop::{EventSource, InsertError, RegistrationToken};
use layer_shika_adapters::platform::slint_interpreter::{ComponentDefinition, ComponentInstance};
use layer_shika_adapters::wayland::{
config::WaylandWindowConfig,
shell_adapter::WaylandWindowingSystem,
};
use layer_shika_adapters::event_loop::calloop_adapter::{
EventLoopAdapter, RuntimeStateAdapter, SystemAdapter,
};
use layer_shika_domain::config::WindowConfig;
use std::result::Result as StdResult;
pub struct EventLoopHandle {
adapter: EventLoopAdapter,
}
impl EventLoopHandle {
pub fn insert_source<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, &mut RuntimeState) -> R + 'static,
{
self.adapter
.insert_source_with_adapter(source, move |event, metadata, adapter| {
let mut runtime_state = RuntimeState { adapter };
callback(event, metadata, &mut runtime_state)
})
}
}
pub struct RuntimeState {
adapter: RuntimeStateAdapter,
}
impl RuntimeState {
#[must_use]
pub fn component_instance(&self) -> &ComponentInstance {
self.adapter.component_instance()
}
pub fn render_frame_if_dirty(&self) -> Result<()> {
Ok(self.adapter.render_frame_if_dirty()?)
}
}
pub struct WindowingSystem {
adapter: SystemAdapter,
}
impl WindowingSystem {
pub(crate) fn new(
component_definition: ComponentDefinition,
config: WindowConfig,
) -> Result<Self> {
let wayland_config = WaylandWindowConfig::from_domain_config(component_definition, config);
let inner_system = WaylandWindowingSystem::new(wayland_config)?;
let adapter = SystemAdapter::new(inner_system);
Ok(Self { adapter })
}
#[must_use]
pub fn event_loop_handle(&self) -> EventLoopHandle {
EventLoopHandle {
adapter: self.adapter.event_loop_handle(),
}
}
pub fn run(&mut self) -> Result<()> {
self.adapter.run()?;
Ok(())
}
#[must_use]
pub const fn component_instance(&self) -> &ComponentInstance {
self.adapter.component_instance()
}
}

12
domain/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "layer-shika-domain"
version = "0.1.0"
edition = "2021"
description = "Domain layer for layer-shika"
license = "AGPL-3.0-or-later"
[lints]
workspace = true
[dependencies]
thiserror = "2.0.17"

39
domain/src/config.rs Normal file
View file

@ -0,0 +1,39 @@
#![allow(clippy::pub_use)]
pub use crate::entities::component::UiComponentHandle;
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)]
pub struct WindowConfig {
pub height: u32,
pub margin: Margins,
pub exclusive_zone: i32,
pub scale_factor: f32,
pub namespace: String,
pub layer: Layer,
pub anchor: AnchorEdges,
}
impl WindowConfig {
#[must_use]
pub fn new() -> Self {
Self {
height: 30,
margin: Margins::default(),
exclusive_zone: -1,
namespace: "layer-shika".to_owned(),
scale_factor: 1.0,
layer: Layer::default(),
anchor: AnchorEdges::default(),
}
}
}
impl Default for WindowConfig {
fn default() -> Self {
Self::new()
}
}

View file

@ -0,0 +1,14 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UiComponentHandle(usize);
impl UiComponentHandle {
#[must_use]
pub const fn new(id: usize) -> Self {
Self(id)
}
#[must_use]
pub const fn id(&self) -> usize {
self.0
}
}

View file

@ -0,0 +1,3 @@
pub mod component;
pub mod surface;
pub mod window;

View file

@ -0,0 +1,14 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SurfaceHandle(usize);
impl SurfaceHandle {
#[must_use]
pub const fn new(id: usize) -> Self {
Self(id)
}
#[must_use]
pub const fn id(&self) -> usize {
self.0
}
}

View file

@ -0,0 +1,14 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WindowHandle(usize);
impl WindowHandle {
#[must_use]
pub const fn new(id: usize) -> Self {
Self(id)
}
#[must_use]
pub const fn id(&self) -> usize {
self.0
}
}

19
domain/src/errors.rs Normal file
View file

@ -0,0 +1,19 @@
use std::result::Result as StdResult;
use thiserror::Error;
pub type Result<T> = StdResult<T, DomainError>;
#[derive(Error, Debug)]
pub enum DomainError {
#[error("Configuration error: {0}")]
Configuration(String),
#[error("Invalid dimensions: {0}")]
InvalidDimensions(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Calculation error: {0}")]
Calculation(String),
}

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

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

View file

@ -0,0 +1,15 @@
#[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 },
}

6
domain/src/lib.rs Normal file
View file

@ -0,0 +1,6 @@
pub mod config;
pub mod entities;
pub mod errors;
pub mod events;
pub mod surface_dimensions;
pub mod value_objects;

View file

@ -1,5 +1,3 @@
use slint::PhysicalSize;
#[derive(Debug, Clone, Copy)]
pub struct SurfaceDimensions {
pub logical_width: u32,
@ -10,6 +8,7 @@ pub struct SurfaceDimensions {
}
impl SurfaceDimensions {
#[must_use]
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
@ -28,12 +27,4 @@ impl SurfaceDimensions {
buffer_scale,
}
}
pub const fn logical_size(&self) -> PhysicalSize {
PhysicalSize::new(self.logical_width, self.logical_height)
}
pub const fn physical_size(&self) -> PhysicalSize {
PhysicalSize::new(self.physical_width, self.physical_height)
}
}

View file

@ -0,0 +1,92 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AnchorEdges(u8);
impl AnchorEdges {
const TOP: u8 = 1 << 0;
const BOTTOM: u8 = 1 << 1;
const LEFT: u8 = 1 << 2;
const RIGHT: u8 = 1 << 3;
#[must_use]
pub const fn new(bits: u8) -> Self {
Self(bits)
}
#[must_use]
pub const fn empty() -> Self {
Self(0)
}
#[must_use]
pub const fn all() -> Self {
Self(Self::TOP | Self::BOTTOM | Self::LEFT | Self::RIGHT)
}
#[must_use]
pub const fn top_bar() -> Self {
Self(Self::TOP | Self::LEFT | Self::RIGHT)
}
#[must_use]
pub const fn bottom_bar() -> Self {
Self(Self::BOTTOM | Self::LEFT | Self::RIGHT)
}
#[must_use]
pub const fn with_top(mut self) -> Self {
self.0 |= Self::TOP;
self
}
#[must_use]
pub const fn with_bottom(mut self) -> Self {
self.0 |= Self::BOTTOM;
self
}
#[must_use]
pub const fn with_left(mut self) -> Self {
self.0 |= Self::LEFT;
self
}
#[must_use]
pub const fn with_right(mut self) -> Self {
self.0 |= Self::RIGHT;
self
}
#[must_use]
pub const fn has_top(&self) -> bool {
self.0 & Self::TOP != 0
}
#[must_use]
pub const fn has_bottom(&self) -> bool {
self.0 & Self::BOTTOM != 0
}
#[must_use]
pub const fn has_left(&self) -> bool {
self.0 & Self::LEFT != 0
}
#[must_use]
pub const fn has_right(&self) -> bool {
self.0 & Self::RIGHT != 0
}
}
impl Default for AnchorEdges {
fn default() -> Self {
Self::top_bar()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Anchor {
Top,
Bottom,
Left,
Right,
}

View file

@ -0,0 +1,20 @@
#[derive(Debug, Clone, Copy)]
pub struct WindowHeight(u32);
impl WindowHeight {
#[must_use]
pub const fn new(height: u32) -> Self {
Self(height)
}
#[must_use]
pub const fn value(&self) -> u32 {
self.0
}
}
impl Default for WindowHeight {
fn default() -> Self {
Self(30)
}
}

View file

@ -0,0 +1,13 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Layer {
Background,
Bottom,
Top,
Overlay,
}
impl Default for Layer {
fn default() -> Self {
Self::Top
}
}

View file

@ -0,0 +1,7 @@
#[derive(Debug, Clone, Copy, Default)]
pub struct Margins {
pub top: i32,
pub right: i32,
pub bottom: i32,
pub left: i32,
}

View file

@ -0,0 +1,4 @@
pub mod anchor;
pub mod dimensions;
pub mod layer;
pub mod margins;

View file

@ -1,8 +0,0 @@
mod errors;
mod reexports;
mod rendering;
mod windowing;
pub use errors::{LayerShikaError, Result};
pub use reexports::*;
pub use windowing::builder::WindowingSystemBuilder as LayerShika;

View file

@ -1,10 +0,0 @@
pub use slint;
pub use slint_interpreter;
pub mod sctk {
pub use smithay_client_toolkit::reexports::*;
}
pub mod wayland_client {
pub use wayland_client::*;
}

View file

@ -1,4 +0,0 @@
pub mod egl_context;
pub mod femtovg_window;
pub mod popup_window;
pub mod slint_platform;

View file

@ -1,93 +0,0 @@
use super::{
config::{Margins, WindowConfig},
WindowingSystem,
};
use crate::errors::Result;
use slint_interpreter::ComponentDefinition;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::{self},
zwlr_layer_surface_v1::{Anchor, KeyboardInteractivity},
};
use std::marker::PhantomData;
pub struct NeedsComponent;
pub struct HasComponent;
pub struct WindowingSystemBuilder<State = NeedsComponent> {
config: WindowConfig,
_state: PhantomData<State>,
}
impl WindowingSystemBuilder<NeedsComponent> {
#[inline]
#[must_use]
pub fn new(component_definition: ComponentDefinition) -> WindowingSystemBuilder<HasComponent> {
WindowingSystemBuilder {
config: WindowConfig::new(component_definition),
_state: PhantomData,
}
}
}
impl WindowingSystemBuilder<HasComponent> {
#[must_use]
pub const fn with_height(mut self, height: u32) -> Self {
self.config.height = height;
self
}
#[must_use]
pub const fn with_layer(mut self, layer: zwlr_layer_shell_v1::Layer) -> Self {
self.config.layer = layer;
self
}
#[must_use]
pub const fn with_margin(mut self, top: i32, right: i32, bottom: i32, left: i32) -> Self {
self.config.margin = Margins {
top,
right,
bottom,
left,
};
self
}
#[must_use]
pub const fn with_anchor(mut self, anchor: Anchor) -> Self {
self.config.anchor = anchor;
self
}
#[must_use]
pub const fn with_keyboard_interactivity(
mut self,
interactivity: KeyboardInteractivity,
) -> Self {
self.config.keyboard_interactivity = interactivity;
self
}
#[must_use]
pub const fn with_exclusive_zone(mut self, zone: i32) -> Self {
self.config.exclusive_zone = zone;
self
}
#[must_use]
pub fn with_namespace(mut self, namespace: String) -> Self {
self.config.namespace = namespace;
self
}
#[must_use]
pub const fn with_scale_factor(mut self, scale_factor: f32) -> Self {
self.config.scale_factor = scale_factor;
self
}
#[allow(clippy::missing_errors_doc)]
pub fn build(self) -> Result<WindowingSystem> {
WindowingSystem::new(self.config)
}
}

View file

@ -1,51 +0,0 @@
use slint_interpreter::ComponentDefinition;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::{self},
zwlr_layer_surface_v1::{Anchor, KeyboardInteractivity},
};
#[derive(Debug, Clone, Copy, Default)]
pub struct Margins {
pub top: i32,
pub right: i32,
pub bottom: i32,
pub left: i32,
}
#[derive(Debug, Clone, Copy)]
pub struct LayerSurfaceParams {
pub anchor: Anchor,
pub margin: Margins,
pub exclusive_zone: i32,
pub keyboard_interactivity: KeyboardInteractivity,
pub height: u32,
}
#[derive(Clone)]
pub struct WindowConfig {
pub height: u32,
pub layer: zwlr_layer_shell_v1::Layer,
pub margin: Margins,
pub anchor: Anchor,
pub keyboard_interactivity: KeyboardInteractivity,
pub exclusive_zone: i32,
pub scale_factor: f32,
pub namespace: String,
pub component_definition: ComponentDefinition,
}
impl WindowConfig {
pub fn new(component_definition: ComponentDefinition) -> Self {
Self {
height: 30,
layer: zwlr_layer_shell_v1::Layer::Top,
margin: Margins::default(),
anchor: Anchor::Top | Anchor::Left | Anchor::Right,
keyboard_interactivity: KeyboardInteractivity::OnDemand,
exclusive_zone: -1,
namespace: "layer-shika".to_owned(),
scale_factor: 1.0,
component_definition,
}
}
}

View file

@ -1,13 +0,0 @@
pub mod builder;
mod config;
mod globals;
mod macros;
mod popup;
mod popup_manager;
mod proxies;
mod state;
mod surface;
mod surface_dimensions;
mod system;
pub use system::WindowingSystem;