git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit6b7b807958b957dc2602f46f145a9361e48accde
parentf7c858e076
authorLucas Galante <[email protected]>
date2026-05-25 20:36
Update system configuration and interface modules

 src/main.rs    | 64 +++++++++++++++++++++++++++++++++++++---------------------
 src/wayland.rs | 23 +++++++++++++++++++++
 src/widget.rs  |  3 ++-
 3 files changed, 66 insertions(+), 24 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 9f6433a..a898a69 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -128,8 +128,7 @@ struct State {
 }
 
 impl State {
-    async fn new(wayland_handle: &'static clear_ui::wayland::WaylandSurfaceHandle, pw: u32, ph: u32) -> Self {
-        let scale = 1.0f64;
+    async fn new(wayland_handle: &'static clear_ui::wayland::WaylandSurfaceHandle, pw: u32, ph: u32, scale: f64) -> Self {
         let lw = pw as f32 / scale as f32;
         let lh = ph as f32 / scale as f32;
         let sw = lw;
@@ -534,8 +533,8 @@ struct AppState {
     pointer: Option<wl_pointer::WlPointer>,
     keyboard: Option<wl_keyboard::WlKeyboard>,
 
-    window: XdgWindow,
-    surface: wl_surface::WlSurface,
+    window: Option<XdgWindow>,
+    surface: Option<wl_surface::WlSurface>,
 
     state: Option<State>,
     exit: bool,
@@ -550,9 +549,12 @@ impl CompositorHandler for AppState {
         _surface: &wl_surface::WlSurface,
         scale_factor: i32,
     ) {
+        _surface.set_buffer_scale(scale_factor);
         if let Some(state) = &mut self.state {
             state.scale = scale_factor as f64;
-            state.resize(state.physical_width, state.physical_height);
+            let pw = (state.width as f64 * state.scale) as u32;
+            let ph = (state.height as f64 * state.scale) as u32;
+            state.resize(pw, ph);
         }
         self.redraw = true;
     }
@@ -908,7 +910,9 @@ impl WindowHandler for AppState {
             let width = w.get();
             let height = h.get();
             if let Some(state) = &mut self.state {
-                state.resize(width, height);
+                let pw = (width as f64 * state.scale) as u32;
+                let ph = (height as f64 * state.scale) as u32;
+                state.resize(pw, ph);
             }
         }
         self.redraw = true;
@@ -963,20 +967,6 @@ fn main() {
     let seat_state = SeatState::new(&globals, &qh);
     let output_state = OutputState::new(&globals, &qh);
 
-    let surface = compositor_state.create_surface(&qh);
-    let window = xdg_shell_state.create_window(surface.clone(), WindowDecorations::None, &qh);
-    window.set_title("Clear UI - Test Window");
-    window.set_app_id("clear-ui");
-    window.set_min_size(Some((1024, 768)));
-    window.commit();
-
-    let wayland_handle = Box::leak(Box::new(clear_ui::wayland::WaylandSurfaceHandle {
-        display_ptr: conn.backend().display_id().as_ptr() as *mut std::ffi::c_void,
-        surface_ptr: surface.id().as_ptr() as *mut std::ffi::c_void,
-    }));
-
-    let state = pollster::block_on(State::new(wayland_handle, 1024, 768));
-
     let mut app = AppState {
         registry_state: RegistryState::new(&globals),
         compositor_state,
@@ -987,13 +977,41 @@ fn main() {
         seats: Vec::new(),
         pointer: None,
         keyboard: None,
-        window,
-        surface,
-        state: Some(state),
+        window: None,
+        surface: None,
+        state: None,
         exit: false,
         redraw: true,
     };
 
+    // Perform a roundtrip to populate output_state with active output scales
+    event_queue.roundtrip(&mut app).unwrap();
+
+    let scale = clear_ui::wayland::detect_scale_factor(&app.output_state);
+
+    let surface = app.compositor_state.create_surface(&qh);
+    surface.set_buffer_scale(scale as i32);
+
+    let pw = (1024.0 * scale) as u32;
+    let ph = (768.0 * scale) as u32;
+
+    let window = app.xdg_shell_state.create_window(surface.clone(), WindowDecorations::None, &qh);
+    window.set_title("Clear UI - Test Window");
+    window.set_app_id("clear-ui");
+    window.set_min_size(Some((pw, ph)));
+    window.commit();
+
+    let wayland_handle = Box::leak(Box::new(clear_ui::wayland::WaylandSurfaceHandle {
+        display_ptr: conn.backend().display_id().as_ptr() as *mut std::ffi::c_void,
+        surface_ptr: surface.id().as_ptr() as *mut std::ffi::c_void,
+    }));
+
+    let state = pollster::block_on(State::new(wayland_handle, pw, ph, scale));
+
+    app.window = Some(window);
+    app.surface = Some(surface);
+    app.state = Some(state);
+
     let mut event_loop = EventLoop::try_new().unwrap();
     let loop_handle = event_loop.handle();
     WaylandSource::new(conn, event_queue).insert(loop_handle).unwrap();
diff --git a/src/wayland.rs b/src/wayland.rs
index d853205..c2aa580 100644
--- a/src/wayland.rs
+++ b/src/wayland.rs
@@ -2,6 +2,7 @@ use raw_window_handle::{
     DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
     RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, WindowHandle,
 };
+use smithay_client_toolkit::output::OutputState;
 
 #[derive(Debug, Clone, Copy)]
 pub struct WaylandSurfaceHandle {
@@ -29,3 +30,25 @@ impl HasWindowHandle for WaylandSurfaceHandle {
         unsafe { Ok(WindowHandle::borrow_raw(raw)) }
     }
 }
+
+/// Helper function to detect the initial display scale factor from Wayland output state.
+/// Iterates over all active outputs and returns the maximum scale factor found (defaulting to 1.0).
+pub fn detect_scale_factor(output_state: &OutputState) -> f64 {
+    let mut max_scale = 1.0;
+    for output in output_state.outputs() {
+        if let Some(info) = output_state.info(&output) {
+            let scale = info.scale_factor as f64;
+            if scale > max_scale {
+                max_scale = scale;
+            }
+        }
+    }
+    max_scale
+}
+
+/// Helper to convert a logical pointer position (from Wayland/SCTK events)
+/// to physical pixel coordinates based on the display scale factor.
+pub fn scale_pointer_pos(pos: (f64, f64), scale: f64) -> (f32, f32) {
+    ((pos.0 * scale) as f32, (pos.1 * scale) as f32)
+}
+
diff --git a/src/widget.rs b/src/widget.rs
index eac70ff..0de965e 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -60,6 +60,7 @@ pub struct KeyEvent {
 
 use crate::colors;
 
+#[derive(Debug, Clone)]
 pub struct TextLabel {
     pub text: String,
     pub x: f32,
@@ -2987,7 +2988,7 @@ impl ScrollBox {
 
     pub fn get_item_draw_y(&self, virtual_y: f32, item_h: f32) -> Option<f32> {
         let draw_y = self.viewport_y + virtual_y - self.scroll_y;
-        if draw_y >= self.viewport_y + 2.0 && draw_y + item_h <= self.viewport_y + self.viewport_h - 2.0 {
+        if draw_y >= self.viewport_y - 1.0 && draw_y + item_h <= self.viewport_y + self.viewport_h + 1.0 {
             Some(draw_y)
         } else {
             None