Skip to main content

reovim_driver_layout/
compositor.rs

1//! Root compositor and window layer compositor traits.
2//!
3//! This module defines the compositor architecture for window layout management.
4//! The design follows a Wayland-inspired model where the runner is a pure event
5//! loop and compositors handle all window geometry concerns.
6//!
7//! # Architecture
8//!
9//! ```text
10//! RootCompositor (manages multiple layers)
11//!     │
12//!     ├── Layer 1 ("main", z_base=100)
13//!     │   └── WindowLayerCompositor
14//!     │       ├── Tiled Zone
15//!     │       ├── Float Zone
16//!     │       └── Overlay Zone
17//!     │
18//!     └── Layer 2 ("float-term", z_base=200)
19//!         └── WindowLayerCompositor
20//!             ├── Tiled Zone
21//!             ├── Float Zone
22//!             └── Overlay Zone
23//! ```
24//!
25//! # Responsibilities
26//!
27//! - **`RootCompositor`**: Layer lifecycle, focus routing between layers
28//! - **`WindowLayerCompositor`**: Window management within a single layer
29
30use {
31    super::layer::{Layer, LayerConfig, LayerId, OverlayConstraints, WindowPlacement, Zone},
32    crate::{NavigateDirection, Rect, SplitDirection, WindowId},
33};
34
35/// Result of compositing all layers.
36///
37/// Contains all information needed to render the complete window layout.
38#[derive(Debug, Clone)]
39pub struct CompositeResult {
40    /// All windows in z-order (lowest first).
41    pub placements: Vec<WindowPlacement>,
42    /// Currently focused window (if any).
43    pub focused: Option<WindowId>,
44    /// Active layer (receives keyboard input).
45    pub active_layer: Option<LayerId>,
46    /// Total screen bounds.
47    pub screen: Rect,
48}
49
50impl CompositeResult {
51    /// Create an empty composite result.
52    #[must_use]
53    #[allow(clippy::missing_const_for_fn)] // Vec::new() is not const
54    pub fn empty(screen: Rect) -> Self {
55        Self {
56            placements: Vec::new(),
57            focused: None,
58            active_layer: None,
59            screen,
60        }
61    }
62
63    /// Get placement for a specific window.
64    #[must_use]
65    pub fn get_placement(&self, window: WindowId) -> Option<&WindowPlacement> {
66        self.placements.iter().find(|p| p.window_id == window)
67    }
68}
69
70/// Root compositor manages multiple layers.
71///
72/// Each layer is a mini-compositor. The root handles:
73/// - Layer creation/destruction
74/// - Layer stacking (z-order)
75/// - Focus routing between layers
76/// - Click-through to lower layers
77///
78/// # Thread Safety
79///
80/// Implementations must be `Send + Sync` to allow compositors to be
81/// shared across async tasks.
82pub trait RootCompositor: Send + Sync {
83    /// Compute final layout for all layers.
84    ///
85    /// Returns all window placements sorted by z-order (lowest first).
86    fn composite(&self, screen: Rect) -> CompositeResult;
87
88    // ========================================================================
89    // Layer Management
90    // ========================================================================
91
92    /// Create a new layer.
93    ///
94    /// Returns the ID of the newly created layer.
95    fn create_layer(&mut self, config: LayerConfig) -> LayerId;
96
97    /// Remove a layer (closes all windows in it).
98    fn remove_layer(&mut self, layer: LayerId);
99
100    /// Get layer by label (for direct focus shortcuts like `<C-w>term`).
101    fn layer_by_label(&self, label: &str) -> Option<LayerId>;
102
103    /// Get all layers in z-order (lowest first).
104    fn layers(&self) -> Vec<&Layer>;
105
106    /// Set layer visibility.
107    fn set_layer_visible(&mut self, layer: LayerId, visible: bool);
108
109    /// Set layer opacity (future: transparency support).
110    fn set_layer_opacity(&mut self, layer: LayerId, opacity: f32);
111
112    /// Move layer in z-order (up/down in stack).
113    fn reorder_layer(&mut self, layer: LayerId, new_z: u16);
114
115    // ========================================================================
116    // Focus Management
117    // ========================================================================
118
119    /// Set active layer (receives keyboard input).
120    fn set_active_layer(&mut self, layer: LayerId);
121
122    /// Get active layer.
123    fn active_layer(&self) -> Option<LayerId>;
124
125    /// Set focus to window (also activates its layer).
126    fn set_focus(&mut self, window: WindowId);
127
128    /// Get focused window in active layer.
129    fn focused(&self) -> Option<WindowId>;
130
131    /// Focus window by clicking at position (handles click-through).
132    ///
133    /// Iterates layers top-to-bottom. Layers with opacity below
134    /// [`CLICK_THROUGH_THRESHOLD`](super::layer::CLICK_THROUGH_THRESHOLD)
135    /// are skipped, allowing clicks to pass through to lower layers.
136    ///
137    /// Returns the window that was focused, or None if clicking empty space.
138    fn focus_at(&mut self, x: u16, y: u16) -> Option<WindowId>;
139
140    // ========================================================================
141    // Per-Layer Operations (delegate to WindowLayerCompositor)
142    // ========================================================================
143
144    /// Get the compositor for a specific layer.
145    ///
146    /// Returns a trait object reference (`&dyn WindowLayerCompositor`) to allow
147    /// polymorphic access to layer-specific operations. Callers can invoke
148    /// any `WindowLayerCompositor` method on the returned reference.
149    ///
150    /// # Example
151    /// ```ignore
152    /// let layer = compositor.layer_compositor(id)?;
153    /// let windows = layer.navigate_tiled(from, Direction::Right);
154    /// ```
155    fn layer_compositor(&self, layer: LayerId) -> Option<&dyn WindowLayerCompositor>;
156
157    /// Get mutable compositor for a layer.
158    ///
159    /// Required for operations that modify layer state (split, close, resize).
160    fn layer_compositor_mut(&mut self, layer: LayerId) -> Option<&mut dyn WindowLayerCompositor>;
161
162    /// Window count across all layers.
163    fn window_count(&self) -> usize;
164
165    /// Update screen dimensions.
166    ///
167    /// Called on terminal resize to update cached screen size.
168    fn set_screen(&mut self, screen: Rect);
169
170    /// Find which layer contains a window.
171    fn layer_of(&self, window: WindowId) -> Option<LayerId>;
172
173    /// Clone this compositor into a new boxed instance.
174    ///
175    /// This allows extracting a fresh owned compositor from shared storage
176    /// (like `ServiceRegistry`'s `Arc<dyn RootCompositor>`). Implementations
177    /// that support `Clone` can simply use `Box::new(self.clone())`.
178    ///
179    /// # Example
180    ///
181    /// ```ignore
182    /// // Get compositor from ServiceRegistry (Arc)
183    /// let arc_compositor = registry.get(&CompositorKey::Root)?;
184    ///
185    /// // Convert to owned Box for DriverSession
186    /// let box_compositor = arc_compositor.boxed_clone();
187    /// driver_session.set_compositor(box_compositor);
188    /// ```
189    fn boxed_clone(&self) -> Box<dyn RootCompositor>;
190}
191
192/// Window layer compositor manages windows within a single layer.
193///
194/// Each layer has three zones: Tiled, Float, Overlay.
195/// This is the mini-compositor for one layer.
196///
197/// # Thread Safety
198///
199/// Implementations must be `Send + Sync` to allow compositors to be
200/// shared across async tasks.
201pub trait WindowLayerCompositor: Send + Sync {
202    /// Get layer ID.
203    fn id(&self) -> LayerId;
204
205    /// Arrange all windows in this layer.
206    ///
207    /// Returns window placements sorted by z-order.
208    fn arrange(&self, bounds: Rect) -> Vec<WindowPlacement>;
209
210    // ========================================================================
211    // Tiled Zone
212    // ========================================================================
213
214    /// Add first tiled window.
215    ///
216    /// Called when the layer has no tiled windows yet.
217    fn add_tiled(&mut self) -> WindowId;
218
219    /// Split a tiled window.
220    ///
221    /// Creates a new window adjacent to `from` in the specified direction.
222    /// Returns the new window's ID, or None if split not possible.
223    fn split_tiled(&mut self, from: WindowId, direction: SplitDirection) -> Option<WindowId>;
224
225    /// Navigate within tiled zone.
226    ///
227    /// Returns the window in the specified direction from `from`,
228    /// or None if there is no neighbor in that direction.
229    fn navigate_tiled(&self, from: WindowId, direction: NavigateDirection) -> Option<WindowId>;
230
231    /// Resize tiled window.
232    ///
233    /// Moves the edge of `window` in `direction` by `delta` units.
234    /// Positive delta expands, negative contracts.
235    fn resize_tiled(&mut self, window: WindowId, direction: NavigateDirection, delta: i16);
236
237    /// Close tiled window.
238    ///
239    /// Returns the window to focus next, or None if this was the last window.
240    fn close_tiled(&mut self, window: WindowId) -> Option<WindowId>;
241
242    /// Equalize tiled windows.
243    ///
244    /// Distributes space equally among all tiled windows.
245    fn equalize_tiled(&mut self);
246
247    /// Cycle through tiled windows.
248    ///
249    /// Returns the next (forward=true) or previous (forward=false) window
250    /// in winnr order.
251    fn cycle_tiled(&self, from: WindowId, forward: bool) -> Option<WindowId>;
252
253    // ========================================================================
254    // Float Zone
255    // ========================================================================
256
257    /// Create floating window.
258    ///
259    /// Creates a new floating window with the specified bounds.
260    fn create_float(&mut self, bounds: Rect) -> WindowId;
261
262    /// Move floating window.
263    fn move_float(&mut self, window: WindowId, x: u16, y: u16);
264
265    /// Resize floating window.
266    fn resize_float(&mut self, window: WindowId, width: u16, height: u16);
267
268    /// Bring to front within float zone.
269    fn raise_float(&mut self, window: WindowId);
270
271    /// Send to back within float zone.
272    fn lower_float(&mut self, window: WindowId);
273
274    /// Close floating window.
275    fn close_float(&mut self, window: WindowId);
276
277    /// Toggle between tiled and floating.
278    ///
279    /// If window is tiled, removes from tiled zone and creates float.
280    /// If window is floating, removes from float zone and adds to tiled.
281    fn toggle_float(&mut self, window: WindowId);
282
283    // ========================================================================
284    // Overlay Zone (#399)
285    // ========================================================================
286
287    /// Show overlay with constraints.
288    ///
289    /// Creates a new overlay positioned according to the constraints.
290    /// Overlays do NOT auto-focus (they are temporary UI above content).
291    fn show_overlay(&mut self, constraints: OverlayConstraints) -> WindowId;
292
293    /// Hide overlay.
294    fn hide_overlay(&mut self, window: WindowId);
295
296    /// Update overlay size.
297    fn resize_overlay(&mut self, window: WindowId, width: u16, height: u16);
298
299    /// Hide all overlays in this layer.
300    fn hide_all_overlays(&mut self);
301
302    // ========================================================================
303    // Focus
304    // ========================================================================
305
306    /// Set focused window within this layer.
307    fn set_focus(&mut self, window: WindowId);
308
309    /// Get focused window.
310    fn focused(&self) -> Option<WindowId>;
311
312    /// Get all windows in a zone.
313    fn windows_in_zone(&self, zone: Zone) -> Vec<WindowId>;
314
315    /// Get window's zone.
316    fn zone_of(&self, window: WindowId) -> Option<Zone>;
317}
318
319/// Error type for compositor operations.
320///
321/// Maps to vim-compatible error codes for user feedback.
322#[derive(Debug, Clone, PartialEq, Eq)]
323pub enum WindowError {
324    /// E444: Cannot close last window.
325    CannotCloseLastWindow,
326    /// E36: No neighbor in direction.
327    NoNeighbor(NavigateDirection),
328    /// E94: Not enough room to split.
329    NotEnoughRoom,
330    /// E36: Cannot resize at edge.
331    CannotResizeAtEdge,
332    /// Generic window not found.
333    WindowNotFound(WindowId),
334    /// Layer not found.
335    LayerNotFound(LayerId),
336}
337
338impl WindowError {
339    /// Get vim-compatible error code.
340    #[must_use]
341    pub const fn vim_code(&self) -> &'static str {
342        match self {
343            Self::CannotCloseLastWindow => "E444",
344            Self::NotEnoughRoom => "E94",
345            Self::NoNeighbor(_)
346            | Self::CannotResizeAtEdge
347            | Self::WindowNotFound(_)
348            | Self::LayerNotFound(_) => "E36",
349        }
350    }
351
352    /// Get user-facing error message.
353    #[must_use]
354    pub fn message(&self) -> String {
355        match self {
356            Self::CannotCloseLastWindow => "Cannot close last window".to_string(),
357            Self::NoNeighbor(dir) => format!("No window in direction: {dir:?}"),
358            Self::NotEnoughRoom => "Not enough room to split".to_string(),
359            Self::CannotResizeAtEdge => "Cannot resize at edge".to_string(),
360            Self::WindowNotFound(id) => format!("Window not found: {}", id.as_usize()),
361            Self::LayerNotFound(id) => format!("Layer not found: {}", id.as_u16()),
362        }
363    }
364}
365
366impl std::fmt::Display for WindowError {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        write!(f, "{}: {}", self.vim_code(), self.message())
369    }
370}
371
372impl std::error::Error for WindowError {}
373
374#[cfg(test)]
375#[path = "compositor_tests.rs"]
376mod tests;