diff --git a/adapters/src/wayland/shell_adapter.rs b/adapters/src/wayland/shell_adapter.rs index a1b1ccc..ccdb346 100644 --- a/adapters/src/wayland/shell_adapter.rs +++ b/adapters/src/wayland/shell_adapter.rs @@ -5,8 +5,8 @@ use crate::wayland::{ surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams}, surfaces::popup_manager::{CreatePopupParams, PopupContext, PopupManager}, surfaces::{ - surface_builder::WindowStateBuilder, - surface_state::{SharedPointerSerial, WindowState}, + scale_coordinator::SharedPointerSerial, surface_builder::WindowStateBuilder, + surface_state::WindowState, }, }; use crate::{ @@ -181,7 +181,9 @@ impl WaylandWindowingSystem { let serial = serial_holder.get(); - let (params, request) = if let Some((request, width, height)) = popup_service_clone.take_pending_popup() { + let (params, request) = if let Some((request, width, height)) = + popup_service_clone.take_pending_popup() + { log::info!( "Using popup request: component='{}', position=({}, {}), size={}x{}, mode={:?}", request.component, @@ -204,18 +206,14 @@ impl WaylandWindowingSystem { } else { log::warn!("Popup creator called without pending popup request - aborting"); return Err(PlatformError::Other( - "No popup request available - cannot create popup without parameters".to_string() + "No popup request available - cannot create popup without parameters" + .to_string(), )); }; let popup_window = popup_service_clone .manager() - .create_popup( - &queue_handle, - &layer_surface, - params, - request, - ) + .create_popup(&queue_handle, &layer_surface, params, request) .map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")))?; let result = Ok(popup_window as Rc); diff --git a/adapters/src/wayland/surfaces/event_router.rs b/adapters/src/wayland/surfaces/event_router.rs new file mode 100644 index 0000000..a6b324b --- /dev/null +++ b/adapters/src/wayland/surfaces/event_router.rs @@ -0,0 +1,51 @@ +use crate::rendering::femtovg::main_window::FemtoVGWindow; +use crate::wayland::services::popup_service::{ActiveWindow, PopupService}; +use layer_shika_domain::value_objects::popup_request::PopupHandle; +use slint::platform::{WindowAdapter, WindowEvent}; +use std::rc::Rc; +use wayland_client::{backend::ObjectId, protocol::wl_surface::WlSurface}; + +pub struct EventRouter { + main_window: Rc, + popup_service: Option>, + main_surface_id: ObjectId, +} + +impl EventRouter { + #[must_use] + pub const fn new(main_window: Rc, main_surface_id: ObjectId) -> Self { + Self { + main_window, + popup_service: None, + main_surface_id, + } + } + + pub fn set_popup_service(&mut self, popup_service: Rc) { + self.popup_service = Some(popup_service); + } + + pub fn find_window_for_surface(&mut self, surface: &WlSurface) { + if let Some(popup_service) = &self.popup_service { + popup_service.find_window_for_surface(surface, &self.main_surface_id); + } + } + + pub fn dispatch_to_active_window(&self, event: WindowEvent) { + if let Some(popup_service) = &self.popup_service { + match popup_service.active_window() { + Some(ActiveWindow::Main) => { + self.main_window.window().dispatch_event(event); + } + Some(ActiveWindow::Popup(index)) => { + if let Some(popup_window) = + popup_service.get_popup_window(PopupHandle::new(index)) + { + popup_window.dispatch_event(event); + } + } + None => {} + } + } + } +} diff --git a/adapters/src/wayland/surfaces/mod.rs b/adapters/src/wayland/surfaces/mod.rs index 7c34f74..64a7313 100644 --- a/adapters/src/wayland/surfaces/mod.rs +++ b/adapters/src/wayland/surfaces/mod.rs @@ -1,6 +1,10 @@ pub mod dimensions; +pub mod event_router; pub mod layer_surface; +pub mod popup_coordinator; pub mod popup_manager; pub mod popup_surface; +pub mod scale_coordinator; pub mod surface_builder; pub mod surface_state; +pub mod window_renderer; diff --git a/adapters/src/wayland/surfaces/popup_coordinator.rs b/adapters/src/wayland/surfaces/popup_coordinator.rs new file mode 100644 index 0000000..623be98 --- /dev/null +++ b/adapters/src/wayland/surfaces/popup_coordinator.rs @@ -0,0 +1,77 @@ +use crate::wayland::services::popup_service::PopupService; +use crate::wayland::surfaces::popup_manager::PopupManager; +use slint::PhysicalSize; +use std::rc::Rc; +use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; + +pub struct PopupCoordinator { + popup_service: Option>, +} + +impl Default for PopupCoordinator { + fn default() -> Self { + Self::new() + } +} + +impl PopupCoordinator { + #[must_use] + pub const fn new() -> Self { + Self { + popup_service: None, + } + } + + pub fn set_popup_service(&mut self, popup_service: Rc) { + self.popup_service = Some(popup_service); + } + + pub fn set_popup_manager(&mut self, popup_manager: Rc) { + self.popup_service = Some(Rc::new(PopupService::new(popup_manager))); + } + + pub fn update_output_size(&self, output_size: PhysicalSize) { + if let Some(popup_service) = &self.popup_service { + popup_service.update_output_size(output_size); + } + } + + pub fn update_scale_factor(&self, scale_factor: f32) { + if let Some(popup_service) = &self.popup_service { + popup_service.update_scale_factor(scale_factor); + } + } + + pub fn clear_active_window(&mut self) { + if let Some(popup_service) = &self.popup_service { + popup_service.clear_active_window(); + } + } + + pub fn clear_active_window_if_popup(&mut self, popup_key: usize) { + if let Some(popup_service) = &self.popup_service { + popup_service.clear_active_window_if_popup(popup_key); + } + } + + pub const fn popup_service(&self) -> &Option> { + &self.popup_service + } + + pub fn popup_manager(&self) -> Option> { + self.popup_service + .as_ref() + .map(|service| Rc::clone(service.manager())) + } + + pub fn update_scale_for_fractional_scale_object( + &self, + fractional_scale_proxy: &WpFractionalScaleV1, + scale_120ths: u32, + ) { + if let Some(popup_service) = &self.popup_service { + popup_service + .update_scale_for_fractional_scale_object(fractional_scale_proxy, scale_120ths); + } + } +} diff --git a/adapters/src/wayland/surfaces/scale_coordinator.rs b/adapters/src/wayland/surfaces/scale_coordinator.rs new file mode 100644 index 0000000..b4b4a1c --- /dev/null +++ b/adapters/src/wayland/surfaces/scale_coordinator.rs @@ -0,0 +1,99 @@ +use log::info; +use slint::LogicalPosition; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct SharedPointerSerial { + serial: RefCell, +} + +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() + } +} + +pub struct ScaleCoordinator { + scale_factor: f32, + current_pointer_position: LogicalPosition, + last_pointer_serial: u32, + shared_pointer_serial: Option>, + has_fractional_scale: bool, +} + +impl ScaleCoordinator { + #[must_use] + pub const fn new(scale_factor: f32, has_fractional_scale: bool) -> Self { + Self { + scale_factor, + current_pointer_position: LogicalPosition::new(0.0, 0.0), + last_pointer_serial: 0, + shared_pointer_serial: None, + has_fractional_scale, + } + } + + pub const fn scale_factor(&self) -> f32 { + self.scale_factor + } + + #[allow(clippy::cast_precision_loss)] + pub fn update_scale_factor(&mut self, scale_120ths: u32) -> f32 { + let new_scale_factor = scale_120ths as f32 / 120.0; + let old_scale_factor = self.scale_factor; + info!( + "Updating scale factor from {} to {} ({}x)", + old_scale_factor, new_scale_factor, scale_120ths + ); + self.scale_factor = new_scale_factor; + new_scale_factor + } + + #[allow(clippy::cast_possible_truncation)] + pub fn set_current_pointer_position(&mut self, physical_x: f64, physical_y: f64) { + let logical_position = if self.has_fractional_scale { + LogicalPosition::new(physical_x as f32, physical_y as f32) + } else { + LogicalPosition::new( + (physical_x / f64::from(self.scale_factor)) as f32, + (physical_y / f64::from(self.scale_factor)) as f32, + ) + }; + self.current_pointer_position = logical_position; + } + + pub const fn current_pointer_position(&self) -> LogicalPosition { + self.current_pointer_position + } + + pub const fn last_pointer_serial(&self) -> u32 { + self.last_pointer_serial + } + + pub fn set_last_pointer_serial(&mut self, serial: u32) { + 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) { + self.shared_pointer_serial = Some(shared_serial); + } +} diff --git a/adapters/src/wayland/surfaces/surface_state.rs b/adapters/src/wayland/surfaces/surface_state.rs index 71accfa..95a51bb 100644 --- a/adapters/src/wayland/surfaces/surface_state.rs +++ b/adapters/src/wayland/surfaces/surface_state.rs @@ -1,81 +1,37 @@ use std::rc::Rc; use super::surface_builder::WindowStateBuilder; -use super::dimensions::SurfaceDimensionsExt; +use super::event_router::EventRouter; +use super::popup_coordinator::PopupCoordinator; use super::popup_manager::PopupManager; +use super::scale_coordinator::{ScaleCoordinator, SharedPointerSerial}; +use super::window_renderer::{WindowRenderer, WindowRendererParams}; use crate::wayland::managed_proxies::{ ManagedWlPointer, ManagedWlSurface, ManagedZwlrLayerSurfaceV1, ManagedWpFractionalScaleV1, ManagedWpViewport, }; -use crate::wayland::services::popup_service::{ActiveWindow, PopupService}; +use crate::wayland::services::popup_service::PopupService; use crate::rendering::femtovg::main_window::FemtoVGWindow; 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::value_objects::popup_request::PopupHandle; -use log::{error, info}; use slint::{LogicalPosition, PhysicalSize, ComponentHandle}; -use slint::platform::{WindowAdapter, WindowEvent}; +use slint::platform::WindowEvent; use slint_interpreter::{ComponentInstance, CompilationResult}; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; use wayland_client::{protocol::wl_surface::WlSurface, Proxy}; use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; -use std::cell::RefCell; - -pub struct SharedPointerSerial { - serial: RefCell, -} - -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 { - FractionalWithViewport, - FractionalOnly, - Integer, -} pub struct WindowState { component_instance: ComponentInstance, compilation_result: Option>, - viewport: Option, - fractional_scale: Option, - layer_surface: ManagedZwlrLayerSurfaceV1, - surface: ManagedWlSurface, #[allow(dead_code)] pointer: ManagedWlPointer, - window: Rc, - height: u32, - exclusive_zone: i32, - popup_service: Option>, - size: PhysicalSize, - logical_size: PhysicalSize, + renderer: WindowRenderer, + event_router: EventRouter, + popup_coordinator: PopupCoordinator, + scale_coordinator: ScaleCoordinator, output_size: PhysicalSize, - current_pointer_position: LogicalPosition, - last_pointer_serial: u32, - shared_pointer_serial: Option>, - scale_factor: f32, } impl WindowState { @@ -131,169 +87,74 @@ impl WindowState { .map(|fs| ManagedWpFractionalScaleV1::new(fs, Rc::clone(&connection))); let layer_surface = ManagedZwlrLayerSurfaceV1::new(layer_surface_rc, Rc::clone(&connection)); - let surface = ManagedWlSurface::new(surface_rc, Rc::clone(&connection)); + let surface = ManagedWlSurface::new(Rc::clone(&surface_rc), Rc::clone(&connection)); let pointer = ManagedWlPointer::new(pointer_rc, connection); + let has_fractional_scale = fractional_scale.is_some(); + let size = builder.size.unwrap_or_default(); + + let renderer = WindowRenderer::new(WindowRendererParams { + window: Rc::clone(&window), + surface, + layer_surface, + viewport, + fractional_scale, + height: builder.height, + exclusive_zone: builder.exclusive_zone, + size, + }); + + let main_surface_id = (*surface_rc).id(); + let event_router = EventRouter::new(Rc::clone(&window), main_surface_id); + let popup_coordinator = PopupCoordinator::new(); + let scale_coordinator = ScaleCoordinator::new(builder.scale_factor, has_fractional_scale); + Ok(Self { component_instance, compilation_result: builder.compilation_result, - viewport, - fractional_scale, - layer_surface, - surface, pointer, - window, - height: builder.height, - exclusive_zone: builder.exclusive_zone, - popup_service: None, - size: builder.size.unwrap_or_default(), - logical_size: PhysicalSize::default(), + renderer, + event_router, + popup_coordinator, + scale_coordinator, output_size: builder.output_size.unwrap_or_default(), - current_pointer_position: LogicalPosition::default(), - last_pointer_serial: 0, - shared_pointer_serial: None, - scale_factor: builder.scale_factor, }) } - const fn determine_scaling_mode(&self) -> ScalingMode { - if self.fractional_scale.is_some() && self.viewport.is_some() { - ScalingMode::FractionalWithViewport - } else if self.fractional_scale.is_some() { - ScalingMode::FractionalOnly - } else { - ScalingMode::Integer - } - } - - #[allow(clippy::cast_precision_loss)] - fn configure_slint_window(&self, dimensions: &SurfaceDimensions, mode: ScalingMode) { - match mode { - ScalingMode::FractionalWithViewport => { - 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, - ))); - } - ScalingMode::FractionalOnly => { - 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, - ))); - } - ScalingMode::Integer => { - self.window.set_scale_factor(self.scale_factor); - self.window.set_size(slint::WindowSize::Physical( - dimensions.to_slint_physical_size(), - )); - } - } - } - - #[allow(clippy::cast_possible_wrap)] - fn configure_wayland_surface(&self, dimensions: &SurfaceDimensions, mode: ScalingMode) { - match mode { - ScalingMode::FractionalWithViewport => { - 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, - ); - } - } - ScalingMode::FractionalOnly | ScalingMode::Integer => { - self.surface.set_buffer_scale(dimensions.buffer_scale()); - } - } - - self.layer_surface - .set_size(dimensions.logical_width(), dimensions.logical_height()); - self.layer_surface.set_exclusive_zone(self.exclusive_zone); - self.surface.commit(); - } - pub fn update_size(&mut self, width: u32, height: u32) { - if width == 0 || height == 0 { - info!("Skipping update_size with zero dimension: {width}x{height}"); - return; - } - - let scale_factor = self.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(), - scale_factor, - dimensions.buffer_scale(), - scaling_mode - ); - - self.configure_slint_window(&dimensions, scaling_mode); - self.configure_wayland_surface(&dimensions, scaling_mode); - - info!("Window physical size: {:?}", self.window.size()); - - self.size = dimensions.to_slint_physical_size(); - self.logical_size = dimensions.to_slint_logical_size(); - self.window.request_redraw(); + let scale_factor = self.scale_coordinator.scale_factor(); + self.renderer.update_size(width, height, scale_factor); } #[allow(clippy::cast_possible_truncation)] pub fn set_current_pointer_position(&mut self, physical_x: f64, physical_y: f64) { - let has_fractional_scale = self.fractional_scale.is_some(); - let logical_position = if has_fractional_scale { - LogicalPosition::new(physical_x as f32, physical_y as f32) - } else { - LogicalPosition::new( - (physical_x / f64::from(self.scale_factor)) as f32, - (physical_y / f64::from(self.scale_factor)) as f32, - ) - }; - self.current_pointer_position = logical_position; + self.scale_coordinator + .set_current_pointer_position(physical_x, physical_y); } - pub const fn size(&self) -> PhysicalSize { - self.size + pub fn size(&self) -> PhysicalSize { + self.renderer.size() } - pub const fn current_pointer_position(&self) -> LogicalPosition { - self.current_pointer_position + pub fn current_pointer_position(&self) -> LogicalPosition { + self.scale_coordinator.current_pointer_position() } pub(crate) fn window(&self) -> Rc { - Rc::clone(&self.window) + Rc::clone(self.renderer.window()) } pub(crate) fn layer_surface(&self) -> Rc { - Rc::clone(self.layer_surface.inner()) + self.renderer.layer_surface() } - pub const fn height(&self) -> u32 { - self.height + pub fn height(&self) -> u32 { + self.renderer.height() } pub fn set_output_size(&mut self, output_size: PhysicalSize) { self.output_size = output_size; - if let Some(popup_service) = &self.popup_service { - popup_service.update_output_size(output_size); - } + self.popup_coordinator.update_output_size(output_size); } pub const fn output_size(&self) -> PhysicalSize { @@ -310,78 +171,54 @@ impl WindowState { } pub fn render_frame_if_dirty(&self) -> Result<()> { - self.window.render_frame_if_dirty() + self.renderer.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; - let old_scale_factor = self.scale_factor; - info!( - "Updating scale factor from {} to {} ({}x)", - old_scale_factor, new_scale_factor, scale_120ths - ); - self.scale_factor = new_scale_factor; + let new_scale_factor = self.scale_coordinator.update_scale_factor(scale_120ths); - if let Some(popup_service) = &self.popup_service { - popup_service.update_scale_factor(new_scale_factor); - } + self.popup_coordinator.update_scale_factor(new_scale_factor); - let current_logical_size = self.logical_size; + let current_logical_size = self.renderer.logical_size(); if current_logical_size.width > 0 && current_logical_size.height > 0 { self.update_size(current_logical_size.width, current_logical_size.height); } } - pub const fn scale_factor(&self) -> f32 { - self.scale_factor + pub fn scale_factor(&self) -> f32 { + self.scale_coordinator.scale_factor() } - pub const fn last_pointer_serial(&self) -> u32 { - self.last_pointer_serial + pub fn last_pointer_serial(&self) -> u32 { + self.scale_coordinator.last_pointer_serial() } pub fn set_last_pointer_serial(&mut self, serial: u32) { - self.last_pointer_serial = serial; - if let Some(ref shared_serial) = self.shared_pointer_serial { - shared_serial.update(serial); - } + self.scale_coordinator.set_last_pointer_serial(serial); } pub fn set_shared_pointer_serial(&mut self, shared_serial: Rc) { - self.shared_pointer_serial = Some(shared_serial); + self.scale_coordinator + .set_shared_pointer_serial(shared_serial); } pub fn set_popup_service(&mut self, popup_service: Rc) { - self.popup_service = Some(popup_service); + self.event_router + .set_popup_service(Rc::clone(&popup_service)); + self.popup_coordinator.set_popup_service(popup_service); } pub fn set_popup_manager(&mut self, popup_manager: Rc) { - self.popup_service = Some(Rc::new(PopupService::new(popup_manager))); + self.popup_coordinator.set_popup_manager(popup_manager); } pub fn find_window_for_surface(&mut self, surface: &WlSurface) { - if let Some(popup_service) = &self.popup_service { - popup_service.find_window_for_surface(surface, &(**self.surface.inner()).id()); - } + self.event_router.find_window_for_surface(surface); } pub fn dispatch_to_active_window(&self, event: WindowEvent) { - if let Some(popup_service) = &self.popup_service { - match popup_service.active_window() { - Some(ActiveWindow::Main) => { - self.window.window().dispatch_event(event); - } - Some(ActiveWindow::Popup(index)) => { - if let Some(popup_window) = - popup_service.get_popup_window(PopupHandle::new(index)) - { - popup_window.dispatch_event(event); - } - } - None => {} - } - } + self.event_router.dispatch_to_active_window(event); } #[allow(clippy::cast_precision_loss)] @@ -392,39 +229,32 @@ impl WindowState { ) { let fractional_scale_id = fractional_scale_proxy.id(); - if let Some(ref main_fractional_scale) = self.fractional_scale { + if let Some(main_fractional_scale) = self.renderer.fractional_scale() { if (**main_fractional_scale.inner()).id() == fractional_scale_id { self.update_scale_factor(scale_120ths); return; } } - if let Some(popup_service) = &self.popup_service { - popup_service - .update_scale_for_fractional_scale_object(fractional_scale_proxy, scale_120ths); - } + self.popup_coordinator + .update_scale_for_fractional_scale_object(fractional_scale_proxy, scale_120ths); } pub fn clear_active_window(&mut self) { - if let Some(popup_service) = &self.popup_service { - popup_service.clear_active_window(); - } + self.popup_coordinator.clear_active_window(); } pub fn clear_active_window_if_popup(&mut self, popup_key: usize) { - if let Some(popup_service) = &self.popup_service { - popup_service.clear_active_window_if_popup(popup_key); - } + self.popup_coordinator + .clear_active_window_if_popup(popup_key); } - pub const fn popup_service(&self) -> &Option> { - &self.popup_service + pub fn popup_service(&self) -> &Option> { + self.popup_coordinator.popup_service() } pub fn popup_manager(&self) -> Option> { - self.popup_service - .as_ref() - .map(|service| Rc::clone(service.manager())) + self.popup_coordinator.popup_manager() } } diff --git a/adapters/src/wayland/surfaces/window_renderer.rs b/adapters/src/wayland/surfaces/window_renderer.rs new file mode 100644 index 0000000..ed87616 --- /dev/null +++ b/adapters/src/wayland/surfaces/window_renderer.rs @@ -0,0 +1,189 @@ +use crate::errors::Result; +use crate::rendering::femtovg::main_window::FemtoVGWindow; +use crate::wayland::managed_proxies::{ + ManagedWlSurface, ManagedZwlrLayerSurfaceV1, ManagedWpFractionalScaleV1, ManagedWpViewport, +}; +use crate::wayland::surfaces::dimensions::SurfaceDimensionsExt; +use layer_shika_domain::surface_dimensions::SurfaceDimensions; +use log::{error, info}; +use slint::PhysicalSize; +use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; +use std::rc::Rc; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ScalingMode { + FractionalWithViewport, + FractionalOnly, + Integer, +} + +pub struct WindowRendererParams { + pub window: Rc, + pub surface: ManagedWlSurface, + pub layer_surface: ManagedZwlrLayerSurfaceV1, + pub viewport: Option, + pub fractional_scale: Option, + pub height: u32, + pub exclusive_zone: i32, + pub size: PhysicalSize, +} + +pub struct WindowRenderer { + window: Rc, + surface: ManagedWlSurface, + layer_surface: ManagedZwlrLayerSurfaceV1, + viewport: Option, + fractional_scale: Option, + height: u32, + exclusive_zone: i32, + size: PhysicalSize, + logical_size: PhysicalSize, +} + +impl WindowRenderer { + #[must_use] + pub fn new(params: WindowRendererParams) -> Self { + Self { + window: params.window, + surface: params.surface, + layer_surface: params.layer_surface, + viewport: params.viewport, + fractional_scale: params.fractional_scale, + height: params.height, + exclusive_zone: params.exclusive_zone, + size: params.size, + logical_size: PhysicalSize::default(), + } + } + + pub fn render_frame_if_dirty(&self) -> Result<()> { + self.window.render_frame_if_dirty() + } + + pub const fn window(&self) -> &Rc { + &self.window + } + + pub fn layer_surface(&self) -> Rc { + Rc::clone(self.layer_surface.inner()) + } + + pub const fn height(&self) -> u32 { + self.height + } + + pub const fn size(&self) -> PhysicalSize { + self.size + } + + const fn determine_scaling_mode(&self) -> ScalingMode { + if self.fractional_scale.is_some() && self.viewport.is_some() { + ScalingMode::FractionalWithViewport + } else if self.fractional_scale.is_some() { + ScalingMode::FractionalOnly + } else { + ScalingMode::Integer + } + } + + #[allow(clippy::cast_precision_loss)] + fn configure_slint_window( + &self, + dimensions: &SurfaceDimensions, + mode: ScalingMode, + scale_factor: f32, + ) { + match mode { + ScalingMode::FractionalWithViewport => { + self.window.set_scale_factor(scale_factor); + self.window + .set_size(slint::WindowSize::Logical(slint::LogicalSize::new( + 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_size(slint::WindowSize::Logical(slint::LogicalSize::new( + dimensions.logical_width() as f32, + dimensions.logical_height() as f32, + ))); + } + ScalingMode::Integer => { + self.window.set_scale_factor(scale_factor); + self.window.set_size(slint::WindowSize::Physical( + dimensions.to_slint_physical_size(), + )); + } + } + } + + #[allow(clippy::cast_possible_wrap)] + fn configure_wayland_surface(&self, dimensions: &SurfaceDimensions, mode: ScalingMode) { + match mode { + ScalingMode::FractionalWithViewport => { + 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, + ); + } + } + ScalingMode::FractionalOnly | ScalingMode::Integer => { + self.surface.set_buffer_scale(dimensions.buffer_scale()); + } + } + + self.layer_surface + .set_size(dimensions.logical_width(), dimensions.logical_height()); + self.layer_surface.set_exclusive_zone(self.exclusive_zone); + self.surface.commit(); + } + + pub fn update_size(&mut self, width: u32, height: u32, scale_factor: f32) { + if width == 0 || height == 0 { + info!("Skipping update_size with zero dimension: {width}x{height}"); + return; + } + + 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(), + scale_factor, + dimensions.buffer_scale(), + scaling_mode + ); + + self.configure_slint_window(&dimensions, scaling_mode, scale_factor); + self.configure_wayland_surface(&dimensions, scaling_mode); + + info!("Window physical size: {:?}", self.window.size()); + + self.size = dimensions.to_slint_physical_size(); + self.logical_size = dimensions.to_slint_logical_size(); + self.window.request_redraw(); + } + + pub const fn logical_size(&self) -> PhysicalSize { + self.logical_size + } + + pub const fn fractional_scale(&self) -> &Option { + &self.fractional_scale + } +}