git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit9e9502e4aa9b4c0f5e6bbe99a463db81c3c800f1
parent50ebf19b6b
authorLucas Galante <[email protected]>
date2026-07-03 11:38
Render target compass zone snap overlays (semicircles) when adjust position mode is enabled

 src/server/output.rs         | 95 ++++++++++++++++++++++++++++++++++++++++++++
 src/server/output_manager.rs | 16 ++++++++
 2 files changed, 111 insertions(+)

diff --git a/src/server/output.rs b/src/server/output.rs
index dc135f1..050bd1b 100644
--- a/src/server/output.rs
+++ b/src/server/output.rs
@@ -142,6 +142,9 @@ pub struct Output {
     pub scene_output: *mut ffi::wlr_scene_output,
     pub background_rect: *mut ffi::wlr_scene_rect,
     pub grid_tree: *mut ffi::wlr_scene_tree,
+    pub adjust_tree: *mut ffi::wlr_scene_tree,
+    pub adjust_rects: Vec<*mut ffi::wlr_scene_rect>,
+    pub last_adjust_mode: bool,
     pub object: *mut ffi::wl_resource, // zcce_output_v1 resource
     pub layer_shell: LayerShellOutput,
     pub lock_render_state: LockRenderState,
@@ -334,6 +337,12 @@ impl Output {
                     }
                     self.grid_rect_pool.clear();
 
+                    if !self.adjust_tree.is_null() {
+                        ffi::wlr_scene_node_destroy(self.adjust_tree as *mut ffi::wlr_scene_node);
+                        self.adjust_tree = std::ptr::null_mut();
+                    }
+                    self.adjust_rects.clear();
+
                     // remove output from windows fullscreen hint
                     for &window in (*self.server).wm.windows.iter() {
                         if let crate::window::FullscreenRequest::Fullscreen(out) = (*window).wm_scheduled.fullscreen_requested {
@@ -399,6 +408,9 @@ impl Output {
             scene_output,
             background_rect: std::ptr::null_mut(),
             grid_tree: std::ptr::null_mut(),
+            adjust_tree: std::ptr::null_mut(),
+            adjust_rects: Vec::new(),
+            last_adjust_mode: false,
             object: std::ptr::null_mut(),
             layer_shell: LayerShellOutput::default(),
             lock_render_state: LockRenderState::Blanked,
@@ -474,6 +486,7 @@ impl Output {
     pub unsafe fn render_and_commit(&mut self) -> Result<(), &'static str> {
         // Update grid node positions and parameters first, which marks the scene output as damaged if changed
         self.draw_grid();
+        self.draw_adjust_overlay();
 
         if !ffi::wlr_scene_output_needs_frame(self.scene_output) {
             return Ok(());
@@ -554,6 +567,82 @@ impl Output {
         }
     }
 
+    pub unsafe fn draw_adjust_overlay(&mut self) {
+        if self.adjust_tree.is_null() {
+            return;
+        }
+
+        let wm = &(*self.server).wm;
+        if wm.adjust_position_mode != self.last_adjust_mode {
+            self.last_adjust_mode = wm.adjust_position_mode;
+            ffi::wlr_output_schedule_frame(self.wlr_output);
+        }
+
+        if !wm.adjust_position_mode {
+            ffi::wlr_scene_node_set_enabled(self.adjust_tree as *mut ffi::wlr_scene_node, false);
+            return;
+        }
+
+        // Enable the overlay tree.
+        ffi::wlr_scene_node_set_enabled(self.adjust_tree as *mut ffi::wlr_scene_node, true);
+        ffi::wlr_scene_node_raise_to_top(self.adjust_tree as *mut ffi::wlr_scene_node);
+
+        let (viewport_w, viewport_h) = self.current.dimensions();
+        let w = viewport_w;
+        let h = viewport_h;
+
+        // Semicircles size: 120 x 120 (so radius = 60).
+        let targets = [
+            // TopLeft (nw): x = 0, y = -60
+            (0, -60),
+            // TopCenter (n): x = w/2 - 60, y = -60
+            (w / 2 - 60, -60),
+            // TopRight (ne): x = w - 120, y = -60
+            (w - 120, -60),
+            // BottomLeft (sw): x = 0, y = h - 60
+            (0, h - 60),
+            // BottomCenter (s): x = w/2 - 60, y = h - 60
+            (w / 2 - 60, h - 60),
+            // BottomRight (se): x = w - 120, y = h - 60
+            (w - 120, h - 60),
+            // Left (w): x = -60, y = h/2 - 60
+            (-60, h / 2 - 60),
+            // Right (e): x = w - 60, y = h/2 - 60
+            (w - 60, h / 2 - 60),
+        ];
+
+        let color: [f32; 4] = [0.4, 0.6, 0.9, 0.5];
+        let color_ptr = color.as_ptr();
+
+        for (idx, &(tx, ty)) in targets.iter().enumerate() {
+            let rect = if idx < self.adjust_rects.len() {
+                let node = self.adjust_rects[idx];
+                ffi::wlr_scene_node_set_enabled(node as *mut ffi::wlr_scene_node, true);
+                ffi::wlr_scene_rect_set_size(node, 120, 120);
+                ffi::wlr_scene_rect_set_color(node, color_ptr);
+                node
+            } else {
+                let node = ffi::wlr_scene_rect_create(self.adjust_tree, 120, 120, color_ptr);
+                if !node.is_null() {
+                    self.adjust_rects.push(node);
+                    // Make it circular!
+                    ffi::river_scene_rect_set_corner_radius(node, 60);
+                }
+                node
+            };
+
+            if !rect.is_null() {
+                ffi::wlr_scene_node_set_position(rect as *mut ffi::wlr_scene_node, tx, ty);
+            }
+        }
+
+        // Disable any extra rects in the pool if we somehow have more
+        for idx in targets.len()..self.adjust_rects.len() {
+            let node = self.adjust_rects[idx];
+            ffi::wlr_scene_node_set_enabled(node as *mut ffi::wlr_scene_node, false);
+        }
+    }
+
     pub unsafe fn draw_grid(&mut self) {
         if self.grid_tree.is_null() {
             return;
@@ -772,6 +861,12 @@ unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut
         (*output).grid_tree = std::ptr::null_mut();
     }
 
+    if !(*output).adjust_tree.is_null() {
+        ffi::wlr_scene_node_destroy((*output).adjust_tree as *mut ffi::wlr_scene_node);
+        (*output).adjust_tree = std::ptr::null_mut();
+    }
+    (*output).adjust_rects.clear();
+
     if !(*output).wlr_output.is_null() {
         ffi::river_wlr_output_set_data((*output).wlr_output, std::ptr::null_mut());
     }
diff --git a/src/server/output_manager.rs b/src/server/output_manager.rs
index d3586cb..e072c8d 100644
--- a/src/server/output_manager.rs
+++ b/src/server/output_manager.rs
@@ -238,6 +238,17 @@ impl OutputManager {
                                 ffi::wlr_scene_node_set_enabled(output.grid_tree as *mut ffi::wlr_scene_node, false);
                             }
                         }
+
+                        if output.adjust_tree.is_null() {
+                            output.adjust_tree = ffi::wlr_scene_tree_create((*server).scene.layers.overlay);
+                        }
+                        if !output.adjust_tree.is_null() {
+                            ffi::wlr_scene_node_set_position(
+                                output.adjust_tree as *mut ffi::wlr_scene_node,
+                                output.sent.x,
+                                output.sent.y,
+                            );
+                        }
                     }
                     OutputStateValue::DisabledHard => {
                         ffi::wlr_output_layout_remove(self.output_layout, wlr_output);
@@ -250,6 +261,11 @@ impl OutputManager {
                             output.grid_tree = std::ptr::null_mut();
                             output.grid_rect_pool.clear();
                         }
+                        if !output.adjust_tree.is_null() {
+                            ffi::wlr_scene_node_destroy(output.adjust_tree as *mut ffi::wlr_scene_node);
+                            output.adjust_tree = std::ptr::null_mut();
+                            output.adjust_rects.clear();
+                        }
                     }
                     OutputStateValue::Destroying => unreachable!(),
                 }