gardesk/gar / 35a4759

Browse files

events: add tiled edge drag resize

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
35a4759bfb9c24e82acac749ac8243cc0dc3bd36
Parents
5584e18
Tree
5aef70d

1 changed file

StatusFile+-
M gar/src/x11/events.rs 153 1
gar/src/x11/events.rsmodified
@@ -217,6 +217,21 @@ pub enum DragState {
217
         /// Original workspace index
217
         /// Original workspace index
218
         workspace: usize,
218
         workspace: usize,
219
     },
219
     },
220
+    /// Dragging on the gap between tiled windows to resize
221
+    TiledResize {
222
+        /// Direction of resize (Right = vertical split, Down = horizontal split)
223
+        direction: Direction,
224
+        /// Starting cursor position (x for horizontal, y for vertical)
225
+        start_pos: i16,
226
+        /// Starting ratio of the split
227
+        start_ratio: f32,
228
+        /// Window whose split we're adjusting
229
+        window: u32,
230
+        /// Total size of the split container (for pixel-to-ratio conversion)
231
+        container_size: u16,
232
+        /// Workspace index
233
+        workspace: usize,
234
+    },
220
 }
235
 }
221
 
236
 
222
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
237
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -576,7 +591,8 @@ impl WindowManager {
576
             let drag_window = match drag {
591
             let drag_window = match drag {
577
                 DragState::Move { window, .. } |
592
                 DragState::Move { window, .. } |
578
                 DragState::Resize { window, .. } |
593
                 DragState::Resize { window, .. } |
579
-                DragState::TiledSwap { window, .. } => *window,
594
+                DragState::TiledSwap { window, .. } |
595
+                DragState::TiledResize { window, .. } => *window,
580
             };
596
             };
581
             if drag_window == event.window {
597
             if drag_window == event.window {
582
                 self.drag_state = None;
598
                 self.drag_state = None;
@@ -705,6 +721,49 @@ impl WindowManager {
705
             }
721
             }
706
         }
722
         }
707
 
723
 
724
+        // Check for edge resize on TILED windows (click on gap between windows, no mod key)
725
+        if !has_mod && event.detail == 1 {
726
+            let screen = self.screen_rect();
727
+            let geometries = self.current_workspace().tree.calculate_geometries(screen);
728
+
729
+            if let Some((window, direction, container_size)) =
730
+                self.find_tiled_edge(event.root_x, event.root_y, &geometries)
731
+            {
732
+                // Get current ratio from tree
733
+                let start_ratio = self
734
+                    .current_workspace()
735
+                    .tree
736
+                    .get_split_ratio(window, direction)
737
+                    .unwrap_or(0.5);
738
+
739
+                let start_pos = match direction {
740
+                    Direction::Left | Direction::Right => event.root_x,
741
+                    Direction::Up | Direction::Down => event.root_y,
742
+                };
743
+
744
+                tracing::debug!(
745
+                    "Starting tiled edge resize: window={}, direction={:?}, ratio={}, container={}",
746
+                    window, direction, start_ratio, container_size
747
+                );
748
+
749
+                self.drag_state = Some(DragState::TiledResize {
750
+                    direction,
751
+                    start_pos,
752
+                    start_ratio,
753
+                    window,
754
+                    container_size,
755
+                    workspace: self.focused_workspace,
756
+                });
757
+
758
+                let cursor = match direction {
759
+                    Direction::Left | Direction::Right => self.conn.cursor_left,
760
+                    Direction::Up | Direction::Down => self.conn.cursor_top,
761
+                };
762
+                self.conn.grab_pointer(Some(cursor))?;
763
+                return Ok(());
764
+            }
765
+        }
766
+
708
         // Check for edge resize on floating windows (click on edge without mod key)
767
         // Check for edge resize on floating windows (click on edge without mod key)
709
         let is_floating_win = self.is_floating(window);
768
         let is_floating_win = self.is_floating(window);
710
         tracing::debug!(
769
         tracing::debug!(
@@ -821,6 +880,9 @@ impl WindowManager {
821
                         self.update_edge_cursor(window, event.root_x, event.root_y)?;
880
                         self.update_edge_cursor(window, event.root_x, event.root_y)?;
822
                     }
881
                     }
823
                 }
882
                 }
883
+                DragState::TiledResize { .. } => {
884
+                    // Layout already applied during motion, nothing special needed
885
+                }
824
             }
886
             }
825
 
887
 
826
             self.conn.flush()?;
888
             self.conn.flush()?;
@@ -954,6 +1016,38 @@ impl WindowManager {
954
                     self.conn.flush()?;
1016
                     self.conn.flush()?;
955
                 }
1017
                 }
956
             }
1018
             }
1019
+            DragState::TiledResize {
1020
+                direction,
1021
+                start_pos,
1022
+                start_ratio,
1023
+                window,
1024
+                container_size,
1025
+                workspace,
1026
+            } => {
1027
+                if *workspace != self.focused_workspace {
1028
+                    return Ok(());
1029
+                }
1030
+
1031
+                let current_pos = match direction {
1032
+                    Direction::Left | Direction::Right => event.root_x,
1033
+                    Direction::Up | Direction::Down => event.root_y,
1034
+                };
1035
+
1036
+                // Convert pixel delta to ratio delta
1037
+                let pixel_delta = current_pos - *start_pos;
1038
+                let ratio_delta = pixel_delta as f32 / *container_size as f32;
1039
+
1040
+                let new_ratio = (*start_ratio + ratio_delta).clamp(0.1, 0.9);
1041
+
1042
+                // Update tree and apply layout
1043
+                let window = *window;
1044
+                let direction = *direction;
1045
+                self.current_workspace_mut()
1046
+                    .tree
1047
+                    .set_split_ratio(window, direction, new_ratio);
1048
+                self.apply_layout()?;
1049
+                self.conn.flush()?;
1050
+            }
957
         }
1051
         }
958
 
1052
 
959
         Ok(())
1053
         Ok(())
@@ -2224,6 +2318,64 @@ impl WindowManager {
2224
         }
2318
         }
2225
     }
2319
     }
2226
 
2320
 
2321
+    /// Find if cursor is in the gap between two adjacent tiled windows.
2322
+    /// Returns (window, direction, container_size) if on a valid shared edge.
2323
+    /// Only matches gaps between windows, NOT outer edges.
2324
+    fn find_tiled_edge(
2325
+        &self,
2326
+        x: i16,
2327
+        y: i16,
2328
+        geometries: &[(u32, Rect)],
2329
+    ) -> Option<(u32, Direction, u16)> {
2330
+        const TILED_EDGE_THRESHOLD: i16 = 8;
2331
+        let gap_tolerance = self.config.gap_inner as i16 + 4;
2332
+
2333
+        for (w1, r1) in geometries {
2334
+            for (w2, r2) in geometries {
2335
+                if w1 >= w2 {
2336
+                    continue;
2337
+                } // Avoid duplicate pairs
2338
+
2339
+                // Check for vertical shared edge (windows side by side)
2340
+                // r1's right edge meets r2's left edge
2341
+                let r1_right = r1.x + r1.width as i16;
2342
+
2343
+                if (r1_right - r2.x).abs() <= gap_tolerance && r1_right <= r2.x {
2344
+                    // Verify vertical overlap (they share a horizontal span)
2345
+                    let y_min = r1.y.max(r2.y);
2346
+                    let y_max = (r1.y + r1.height as i16).min(r2.y + r2.height as i16);
2347
+                    if y_min < y_max && y >= y_min && y < y_max {
2348
+                        // Cursor must be in the gap between them
2349
+                        let gap_center = (r1_right + r2.x) / 2;
2350
+                        if (x - gap_center).abs() <= TILED_EDGE_THRESHOLD {
2351
+                            let container_width = (r1.width + r2.width) as u16;
2352
+                            return Some((*w1, Direction::Right, container_width));
2353
+                        }
2354
+                    }
2355
+                }
2356
+
2357
+                // Check for horizontal shared edge (windows stacked vertically)
2358
+                // r1's bottom edge meets r2's top edge
2359
+                let r1_bottom = r1.y + r1.height as i16;
2360
+
2361
+                if (r1_bottom - r2.y).abs() <= gap_tolerance && r1_bottom <= r2.y {
2362
+                    // Verify horizontal overlap (they share a vertical span)
2363
+                    let x_min = r1.x.max(r2.x);
2364
+                    let x_max = (r1.x + r1.width as i16).min(r2.x + r2.width as i16);
2365
+                    if x_min < x_max && x >= x_min && x < x_max {
2366
+                        // Cursor must be in the gap between them
2367
+                        let gap_center = (r1_bottom + r2.y) / 2;
2368
+                        if (y - gap_center).abs() <= TILED_EDGE_THRESHOLD {
2369
+                            let container_height = (r1.height + r2.height) as u16;
2370
+                            return Some((*w1, Direction::Down, container_height));
2371
+                        }
2372
+                    }
2373
+                }
2374
+            }
2375
+        }
2376
+        None
2377
+    }
2378
+
2227
     fn get_floating_geometry(&self, window: u32) -> Rect {
2379
     fn get_floating_geometry(&self, window: u32) -> Rect {
2228
         self.windows
2380
         self.windows
2229
             .get(&window)
2381
             .get(&window)