diff --git a/adapters/src/wayland/config.rs b/adapters/src/wayland/config.rs index f9e418b..f04c2a0 100644 --- a/adapters/src/wayland/config.rs +++ b/adapters/src/wayland/config.rs @@ -40,7 +40,7 @@ impl WaylandWindowConfig { domain_config: DomainWindowConfig, ) -> Self { Self { - height: domain_config.height, + height: domain_config.height.value(), layer: convert_layer(domain_config.layer), margin: domain_config.margin, anchor: convert_anchor(domain_config.anchor), @@ -48,7 +48,7 @@ impl WaylandWindowConfig { domain_config.keyboard_interactivity, ), exclusive_zone: domain_config.exclusive_zone, - scale_factor: domain_config.scale_factor, + scale_factor: domain_config.scale_factor.value(), namespace: domain_config.namespace, component_definition, compilation_result, diff --git a/adapters/src/wayland/surfaces/dimensions.rs b/adapters/src/wayland/surfaces/dimensions.rs index 5e54b61..75cf1cb 100644 --- a/adapters/src/wayland/surfaces/dimensions.rs +++ b/adapters/src/wayland/surfaces/dimensions.rs @@ -2,16 +2,16 @@ use layer_shika_domain::surface_dimensions::SurfaceDimensions; use slint::PhysicalSize; pub trait SurfaceDimensionsExt { - fn logical_size(&self) -> PhysicalSize; - fn physical_size(&self) -> PhysicalSize; + fn to_slint_logical_size(&self) -> PhysicalSize; + fn to_slint_physical_size(&self) -> PhysicalSize; } impl SurfaceDimensionsExt for SurfaceDimensions { - fn logical_size(&self) -> PhysicalSize { - PhysicalSize::new(self.logical_width, self.logical_height) + fn to_slint_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) + fn to_slint_physical_size(&self) -> PhysicalSize { + PhysicalSize::new(self.physical_width(), self.physical_height()) } } diff --git a/adapters/src/wayland/surfaces/surface_state.rs b/adapters/src/wayland/surfaces/surface_state.rs index f4ae8ea..61b0117 100644 --- a/adapters/src/wayland/surfaces/surface_state.rs +++ b/adapters/src/wayland/surfaces/surface_state.rs @@ -12,7 +12,7 @@ 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 log::info; +use log::{error, info}; use slint::{LogicalPosition, PhysicalSize, ComponentHandle}; use slint::platform::{WindowAdapter, WindowEvent}; use slint_interpreter::{ComponentInstance, CompilationResult}; @@ -179,22 +179,22 @@ impl WindowState { self.window.set_scale_factor(self.scale_factor); self.window .set_size(slint::WindowSize::Logical(slint::LogicalSize::new( - dimensions.logical_width as f32, - dimensions.logical_height as f32, + dimensions.logical_width() as f32, + dimensions.logical_height() as f32, ))); } ScalingMode::FractionalOnly => { - self.window.set_scale_factor(dimensions.buffer_scale as f32); + self.window.set_scale_factor(dimensions.buffer_scale() as f32); self.window .set_size(slint::WindowSize::Logical(slint::LogicalSize::new( - dimensions.logical_width as f32, - dimensions.logical_height as f32, + dimensions.logical_width() as f32, + dimensions.logical_height() as f32, ))); } ScalingMode::Integer => { self.window.set_scale_factor(self.scale_factor); self.window - .set_size(slint::WindowSize::Physical(dimensions.physical_size())); + .set_size(slint::WindowSize::Physical(dimensions.to_slint_physical_size())); } } } @@ -206,18 +206,18 @@ impl WindowState { self.surface.set_buffer_scale(1); if let Some(viewport) = &self.viewport { viewport.set_destination( - dimensions.logical_width as i32, - dimensions.logical_height as i32, + dimensions.logical_width() as i32, + dimensions.logical_height() as i32, ); } } ScalingMode::FractionalOnly | ScalingMode::Integer => { - self.surface.set_buffer_scale(dimensions.buffer_scale); + self.surface.set_buffer_scale(dimensions.buffer_scale()); } } self.layer_surface - .set_size(dimensions.logical_width, dimensions.logical_height); + .set_size(dimensions.logical_width(), dimensions.logical_height()); self.layer_surface.set_exclusive_zone(self.exclusive_zone); self.surface.commit(); } @@ -229,17 +229,23 @@ impl WindowState { } let scale_factor = self.scale_factor(); - let dimensions = SurfaceDimensions::calculate(width, height, scale_factor); + let dimensions = match SurfaceDimensions::calculate(width, height, scale_factor) { + Ok(d) => d, + Err(e) => { + error!("Failed to calculate surface dimensions: {e}"); + return; + } + }; let scaling_mode = self.determine_scaling_mode(); info!( "Updating window size: logical {}x{}, physical {}x{}, scale {}, buffer_scale {}, mode {:?}", - dimensions.logical_width, - dimensions.logical_height, - dimensions.physical_width, - dimensions.physical_height, + dimensions.logical_width(), + dimensions.logical_height(), + dimensions.physical_width(), + dimensions.physical_height(), scale_factor, - dimensions.buffer_scale, + dimensions.buffer_scale(), scaling_mode ); @@ -248,8 +254,8 @@ impl WindowState { info!("Window physical size: {:?}", self.window.size()); - self.size = dimensions.physical_size(); - self.logical_size = dimensions.logical_size(); + self.size = dimensions.to_slint_physical_size(); + self.logical_size = dimensions.to_slint_logical_size(); self.window.request_redraw(); } diff --git a/composition/src/builder.rs b/composition/src/builder.rs index 55524b6..8cc4268 100644 --- a/composition/src/builder.rs +++ b/composition/src/builder.rs @@ -3,7 +3,7 @@ use crate::system::WindowingSystem; use layer_shika_adapters::platform::slint_interpreter::{CompilationResult, Compiler}; use layer_shika_domain::errors::DomainError; use layer_shika_domain::prelude::{ - AnchorEdges, KeyboardInteractivity, Layer, Margins, WindowConfig, + AnchorEdges, KeyboardInteractivity, Layer, Margins, ScaleFactor, WindowConfig, WindowHeight, }; use spin_on::spin_on; use std::path::{Path, PathBuf}; @@ -135,10 +135,9 @@ impl LayerShika { } impl LayerShika { - #[must_use] - pub const fn with_height(mut self, height: u32) -> Self { - self.config.height = height; - self + pub fn with_height(mut self, height: u32) -> Result { + self.config.height = WindowHeight::new(height)?; + Ok(self) } #[must_use] @@ -176,10 +175,9 @@ impl LayerShika { self } - #[must_use] - pub const fn with_scale_factor(mut self, scale_factor: f32) -> Self { - self.config.scale_factor = scale_factor; - self + pub fn with_scale_factor(mut self, scale_factor: f32) -> Result { + self.config.scale_factor = ScaleFactor::new(scale_factor)?; + Ok(self) } #[must_use] diff --git a/domain/src/config.rs b/domain/src/config.rs index 9c1908c..92ce3ec 100644 --- a/domain/src/config.rs +++ b/domain/src/config.rs @@ -1,14 +1,16 @@ +use crate::dimensions::ScaleFactor; use crate::value_objects::anchor::AnchorEdges; +use crate::value_objects::dimensions::WindowHeight; use crate::value_objects::keyboard_interactivity::KeyboardInteractivity; use crate::value_objects::layer::Layer; use crate::value_objects::margins::Margins; #[derive(Debug, Clone)] pub struct WindowConfig { - pub height: u32, + pub height: WindowHeight, pub margin: Margins, pub exclusive_zone: i32, - pub scale_factor: f32, + pub scale_factor: ScaleFactor, pub namespace: String, pub layer: Layer, pub anchor: AnchorEdges, @@ -19,11 +21,11 @@ impl WindowConfig { #[must_use] pub fn new() -> Self { Self { - height: 30, + height: WindowHeight::default(), margin: Margins::default(), exclusive_zone: -1, namespace: "layer-shika".to_owned(), - scale_factor: 1.0, + scale_factor: ScaleFactor::default(), layer: Layer::default(), anchor: AnchorEdges::default(), keyboard_interactivity: KeyboardInteractivity::default(), diff --git a/domain/src/dimensions.rs b/domain/src/dimensions.rs new file mode 100644 index 0000000..e56cc89 --- /dev/null +++ b/domain/src/dimensions.rs @@ -0,0 +1,233 @@ +use crate::errors::DomainError; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct LogicalSize { + width: f32, + height: f32, +} + +impl LogicalSize { + pub fn new(width: f32, height: f32) -> Result { + if width <= 0.0 || height <= 0.0 { + return Err(DomainError::InvalidInput { + message: format!("Dimensions must be positive, got width={width}, height={height}"), + }); + } + if !width.is_finite() || !height.is_finite() { + return Err(DomainError::InvalidInput { + message: "Dimensions must be finite values".to_string(), + }); + } + Ok(Self { width, height }) + } + + pub const fn from_raw(width: f32, height: f32) -> Self { + Self { width, height } + } + + pub const fn width(&self) -> f32 { + self.width + } + + pub const fn height(&self) -> f32 { + self.height + } + + pub fn to_physical(&self, scale_factor: ScaleFactor) -> PhysicalSize { + scale_factor.to_physical(*self) + } + + pub fn as_tuple(&self) -> (f32, f32) { + (self.width, self.height) + } +} + +impl Default for LogicalSize { + fn default() -> Self { + Self { + width: 120.0, + height: 120.0, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PhysicalSize { + width: u32, + height: u32, +} + +impl PhysicalSize { + pub fn new(width: u32, height: u32) -> Result { + if width == 0 || height == 0 { + return Err(DomainError::InvalidDimensions { width, height }); + } + Ok(Self { width, height }) + } + + pub const fn from_raw(width: u32, height: u32) -> Self { + Self { width, height } + } + + pub const fn width(&self) -> u32 { + self.width + } + + pub const fn height(&self) -> u32 { + self.height + } + + pub fn to_logical(&self, scale_factor: ScaleFactor) -> LogicalSize { + scale_factor.to_logical(*self) + } + + pub fn as_tuple(&self) -> (u32, u32) { + (self.width, self.height) + } +} + +impl Default for PhysicalSize { + fn default() -> Self { + Self { + width: 120, + height: 120, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ScaleFactor(f32); + +impl ScaleFactor { + pub fn new(factor: f32) -> Result { + if factor <= 0.0 { + return Err(DomainError::InvalidInput { + message: format!("Scale factor must be positive, got {factor}"), + }); + } + if !factor.is_finite() { + return Err(DomainError::InvalidInput { + message: "Scale factor must be a finite value".to_string(), + }); + } + Ok(Self(factor)) + } + + pub const fn from_raw(factor: f32) -> Self { + Self(factor) + } + + pub const fn value(&self) -> f32 { + self.0 + } + + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + pub fn to_physical(&self, logical: LogicalSize) -> PhysicalSize { + let width = (logical.width * self.0).round() as u32; + let height = (logical.height * self.0).round() as u32; + PhysicalSize::from_raw(width.max(1), height.max(1)) + } + + #[allow(clippy::cast_precision_loss)] + pub fn to_logical(&self, physical: PhysicalSize) -> LogicalSize { + let width = physical.width as f32 / self.0; + let height = physical.height as f32 / self.0; + LogicalSize::from_raw(width, height) + } + + #[allow(clippy::cast_possible_truncation)] + pub fn buffer_scale(&self) -> i32 { + self.0.round() as i32 + } + + pub fn scale_coordinate(&self, logical_coord: f32) -> f32 { + logical_coord * self.0 + } + + pub fn unscale_coordinate(&self, physical_coord: f32) -> f32 { + physical_coord / self.0 + } +} + +impl Default for ScaleFactor { + fn default() -> Self { + Self(1.0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct LogicalPosition { + x: f32, + y: f32, +} + +impl LogicalPosition { + pub fn new(x: f32, y: f32) -> Self { + Self { x, y } + } + + pub const fn x(&self) -> f32 { + self.x + } + + pub const fn y(&self) -> f32 { + self.y + } + + #[allow(clippy::cast_possible_truncation)] + pub fn to_physical(&self, scale_factor: ScaleFactor) -> PhysicalPosition { + PhysicalPosition::new( + (self.x * scale_factor.value()).round() as i32, + (self.y * scale_factor.value()).round() as i32, + ) + } + + pub fn as_tuple(&self) -> (f32, f32) { + (self.x, self.y) + } +} + +impl Default for LogicalPosition { + fn default() -> Self { + Self { x: 0.0, y: 0.0 } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PhysicalPosition { + x: i32, + y: i32, +} + +impl PhysicalPosition { + pub const fn new(x: i32, y: i32) -> Self { + Self { x, y } + } + + pub const fn x(&self) -> i32 { + self.x + } + + pub const fn y(&self) -> i32 { + self.y + } + + #[allow(clippy::cast_precision_loss)] + pub fn to_logical(&self, scale_factor: ScaleFactor) -> LogicalPosition { + LogicalPosition::new( + self.x as f32 / scale_factor.value(), + self.y as f32 / scale_factor.value(), + ) + } + + pub fn as_tuple(&self) -> (i32, i32) { + (self.x, self.y) + } +} + +#[allow(clippy::derivable_impls)] +impl Default for PhysicalPosition { + fn default() -> Self { + Self { x: 0, y: 0 } + } +} diff --git a/domain/src/lib.rs b/domain/src/lib.rs index 79e5089..c3dab49 100644 --- a/domain/src/lib.rs +++ b/domain/src/lib.rs @@ -1,4 +1,5 @@ pub mod config; +pub mod dimensions; pub mod entities; pub mod errors; pub mod ports; diff --git a/domain/src/prelude.rs b/domain/src/prelude.rs index f2737d3..738e274 100644 --- a/domain/src/prelude.rs +++ b/domain/src/prelude.rs @@ -1,6 +1,9 @@ #![allow(clippy::pub_use)] pub use crate::config::WindowConfig; +pub use crate::dimensions::{ + LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, ScaleFactor, +}; pub use crate::entities::component::UiComponentHandle; pub use crate::entities::surface::SurfaceHandle; pub use crate::entities::window::WindowHandle; diff --git a/domain/src/surface_dimensions.rs b/domain/src/surface_dimensions.rs index 175c550..f1c3e04 100644 --- a/domain/src/surface_dimensions.rs +++ b/domain/src/surface_dimensions.rs @@ -1,30 +1,62 @@ +use crate::dimensions::{LogicalSize, PhysicalSize, ScaleFactor}; +use crate::errors::Result; + #[derive(Debug, Clone, Copy)] pub struct SurfaceDimensions { - pub logical_width: u32, - pub logical_height: u32, - pub physical_width: u32, - pub physical_height: u32, - pub buffer_scale: i32, + logical: LogicalSize, + physical: PhysicalSize, + scale_factor: ScaleFactor, } impl SurfaceDimensions { - #[must_use] - #[allow( - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::cast_precision_loss - )] - pub fn calculate(logical_width: u32, logical_height: u32, scale_factor: f32) -> Self { - let physical_width = (logical_width as f32 * scale_factor).round() as u32; - let physical_height = (logical_height as f32 * scale_factor).round() as u32; - let buffer_scale = scale_factor.round() as i32; + #[allow(clippy::cast_precision_loss)] + pub fn calculate( + logical_width: u32, + logical_height: u32, + scale_factor: f32, + ) -> Result { + let logical = LogicalSize::new(logical_width as f32, logical_height as f32)?; + let scale = ScaleFactor::new(scale_factor)?; + let physical = scale.to_physical(logical); - Self { - logical_width, - logical_height, - physical_width, - physical_height, - buffer_scale, - } + Ok(Self { + logical, + physical, + scale_factor: scale, + }) + } + + pub const fn logical_size(&self) -> LogicalSize { + self.logical + } + + pub const fn physical_size(&self) -> PhysicalSize { + self.physical + } + + pub const fn scale_factor(&self) -> ScaleFactor { + self.scale_factor + } + + pub fn buffer_scale(&self) -> i32 { + self.scale_factor.buffer_scale() + } + + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + pub fn logical_width(&self) -> u32 { + self.logical.width() as u32 + } + + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + pub fn logical_height(&self) -> u32 { + self.logical.height() as u32 + } + + pub fn physical_width(&self) -> u32 { + self.physical.width() + } + + pub fn physical_height(&self) -> u32 { + self.physical.height() } } diff --git a/domain/src/value_objects/dimensions.rs b/domain/src/value_objects/dimensions.rs index a207e8d..65efefa 100644 --- a/domain/src/value_objects/dimensions.rs +++ b/domain/src/value_objects/dimensions.rs @@ -1,13 +1,23 @@ -#[derive(Debug, Clone, Copy)] +use crate::errors::{DomainError, Result}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct WindowHeight(u32); impl WindowHeight { - #[must_use] - pub const fn new(height: u32) -> Self { + pub fn new(height: u32) -> Result { + if height == 0 { + return Err(DomainError::InvalidDimensions { + width: 0, + height: 0, + }); + } + Ok(Self(height)) + } + + pub const fn from_raw(height: u32) -> Self { Self(height) } - #[must_use] pub const fn value(&self) -> u32 { self.0 } @@ -18,3 +28,11 @@ impl Default for WindowHeight { Self(30) } } + +impl TryFrom for WindowHeight { + type Error = DomainError; + + fn try_from(height: u32) -> Result { + Self::new(height) + } +} diff --git a/domain/src/value_objects/popup_config.rs b/domain/src/value_objects/popup_config.rs index 910fd4b..7e0c10c 100644 --- a/domain/src/value_objects/popup_config.rs +++ b/domain/src/value_objects/popup_config.rs @@ -1,19 +1,16 @@ use super::popup_positioning_mode::PopupPositioningMode; +use crate::dimensions::{LogicalPosition, LogicalSize}; #[derive(Debug, Clone, Copy)] pub struct PopupConfig { - reference_x: f32, - reference_y: f32, - width: f32, - height: f32, + reference_position: LogicalPosition, + popup_size: LogicalSize, + output_size: LogicalSize, positioning_mode: PopupPositioningMode, - output_width: f32, - output_height: f32, } impl PopupConfig { - #[must_use] - pub const fn new( + pub fn new( reference_x: f32, reference_y: f32, width: f32, @@ -23,62 +20,68 @@ impl PopupConfig { output_height: f32, ) -> Self { Self { - reference_x, - reference_y, - width, - height, + reference_position: LogicalPosition::new(reference_x, reference_y), + popup_size: LogicalSize::from_raw(width, height), + output_size: LogicalSize::from_raw(output_width, output_height), positioning_mode, - output_width, - output_height, } } - #[must_use] + pub const fn reference_position(&self) -> LogicalPosition { + self.reference_position + } + pub const fn reference_x(&self) -> f32 { - self.reference_x + self.reference_position.x() } - #[must_use] pub const fn reference_y(&self) -> f32 { - self.reference_y + self.reference_position.y() + } + + pub const fn popup_size(&self) -> LogicalSize { + self.popup_size } - #[must_use] pub const fn width(&self) -> f32 { - self.width + self.popup_size.width() } - #[must_use] pub const fn height(&self) -> f32 { - self.height + self.popup_size.height() + } + + pub const fn output_size(&self) -> LogicalSize { + self.output_size } - #[must_use] pub const fn positioning_mode(&self) -> PopupPositioningMode { self.positioning_mode } - #[must_use] + pub fn calculated_top_left_position(&self) -> LogicalPosition { + LogicalPosition::new(self.calculated_top_left_x(), self.calculated_top_left_y()) + } + pub fn calculated_top_left_x(&self) -> f32 { let unclamped_x = if self.positioning_mode.center_x() { - self.reference_x - (self.width / 2.0) + self.reference_x() - (self.width() / 2.0) } else { - self.reference_x + self.reference_x() }; - let max_x = self.output_width - self.width; + let max_x = self.output_size.width() - self.width(); unclamped_x.max(0.0).min(max_x) } - #[must_use] pub fn calculated_top_left_y(&self) -> f32 { let unclamped_y = if self.positioning_mode.center_y() { - self.reference_y - (self.height / 2.0) + self.reference_y() - (self.height() / 2.0) } else { - self.reference_y + self.reference_y() }; - let max_y = self.output_height - self.height; + let max_y = self.output_size.height() - self.height(); unclamped_y.max(0.0).min(max_y) } } diff --git a/domain/src/value_objects/popup_dimensions.rs b/domain/src/value_objects/popup_dimensions.rs index 20d5341..6ab7248 100644 --- a/domain/src/value_objects/popup_dimensions.rs +++ b/domain/src/value_objects/popup_dimensions.rs @@ -1,42 +1,42 @@ +use crate::dimensions::LogicalSize; use crate::errors::{DomainError, Result}; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct PopupDimensions { - width: f32, - height: f32, + size: LogicalSize, } impl PopupDimensions { - #[must_use] - pub const fn new(width: f32, height: f32) -> Self { - Self { width, height } + pub fn new(width: f32, height: f32) -> Result { + let size = LogicalSize::new(width, height)?; + Ok(Self { size }) + } + + pub const fn from_logical(size: LogicalSize) -> Self { + Self { size } } - #[must_use] pub const fn width(&self) -> f32 { - self.width + self.size.width() } - #[must_use] pub const fn height(&self) -> f32 { - self.height + self.size.height() } - pub fn validate(&self) -> Result<()> { - if self.width <= 0.0 || self.height <= 0.0 { - return Err(DomainError::Configuration { - message: format!( - "Invalid popup dimensions: width={}, height={}. Both must be positive.", - self.width, self.height - ), - }); - } - Ok(()) + pub const fn logical_size(&self) -> LogicalSize { + self.size + } + + pub fn as_tuple(&self) -> (f32, f32) { + self.size.as_tuple() } } -impl Default for PopupDimensions { - fn default() -> Self { - Self::new(120.0, 120.0) +impl TryFrom<(f32, f32)> for PopupDimensions { + type Error = DomainError; + + fn try_from((width, height): (f32, f32)) -> Result { + Self::new(width, height) } }