docs: make scrolling the default layout

This commit is contained in:
Samuel Onoja committed 2026-09-25 01:53:08 +01:00
1 parent 5fce7227ed
commit 4f72fdfcf4
2 files changed
+151 -54

No files matched your search

+6 -4
View File
@@ -1,9 +1,11 @@
# Ferese # Ferese
Ferese is an experimental Wayland tiling compositor written in Rust with Ferese is an experimental Wayland scrolling compositor written in Rust with
Smithay. The nested M0–M3 path is complete, including tiling, workspaces, and Smithay. The current implementation includes the original tree layout,
timestamp-driven animated geometry. Development now targets the M4 early workspaces, and timestamp-driven animated geometry. The specification now makes
hardware-session acceptance pass and M5 native interoperability in the scrolling columns the default and retains tree tiling as an optional mode. DRM
multi-output and native interoperability are being completed before that layout
transition in the
[window-manager specification](docs/ferese-window-manager-spec.md). [window-manager specification](docs/ferese-window-manager-spec.md).
## Development ## Development
+145 -50
View File
@@ -7,7 +7,7 @@ Display protocol: Wayland
## 1. Purpose ## 1. Purpose
Ferese is a Wayland tiling compositor for a conventional, keyboard-friendly Ferese is a Wayland scrolling compositor for a conventional, keyboard-friendly
desktop. It combines predictable window management with compositor-owned rounded desktop. It combines predictable window management with compositor-owned rounded
geometry, shadows, animation, transparency, and semantic surface materials. geometry, shadows, animation, transparency, and semantic surface materials.
@@ -52,7 +52,7 @@ Ferese v0 is the smallest independently usable native-Wayland release. It MUST
provide: provide:
- native Wayland compositing; - native Wayland compositing;
- automatic and manually adjustable tiling; - automatic scrolling columns with optional tree tiling;
- floating and fullscreen windows; - floating and fullscreen windows;
- multiple workspaces and outputs; - multiple workspaces and outputs;
- keyboard and pointer input; - keyboard and pointer input;
@@ -294,18 +294,18 @@ pub struct Window {
} }
pub enum WindowPlacement { pub enum WindowPlacement {
Tiled, Layout,
Floating { rect: RectF }, Floating { rect: RectF },
} }
``` ```
Fullscreen is a presentation state layered over the preserved tiled or floating Fullscreen is a presentation state layered over the preserved layout or floating
placement. Entering fullscreen MUST NOT remove a tiled window from its layout placement. Entering fullscreen MUST NOT remove a managed window from its
tree or a floating window from its floating list. At most one window may be workspace layout or a floating window from its floating list. At most one window
fullscreen on a workspace. While fullscreen is active, that window is the sole may be fullscreen on a workspace. While fullscreen is active, that window is the
normal managed-window content presented for the workspace, although permitted sole normal managed-window content presented for the workspace, although
shell overlays and popups may still appear. Leaving fullscreen restores the permitted shell overlays and popups may still appear. Leaving fullscreen
preserved placement without reconstructing it. restores the preserved placement without reconstructing it.
A window MUST NOT independently store an output. Its output is derived from its A window MUST NOT independently store an output. Its output is derived from its
workspace, preventing stale ownership after workspace migration. Authoritative workspace, preventing stale ownership after workspace migration. Authoritative
@@ -319,7 +319,7 @@ pub struct Workspace {
pub id: WorkspaceId, pub id: WorkspaceId,
pub name: String, pub name: String,
pub output: Option<OutputId>, pub output: Option<OutputId>,
pub root: Option<NodeId>, pub layout: WorkspaceLayout,
pub floating: Vec<WindowId>, pub floating: Vec<WindowId>,
pub last_focused: Option<WindowId>, pub last_focused: Option<WindowId>,
} }
@@ -348,10 +348,10 @@ These invariants MUST hold after every state transaction:
2. Every attached workspace belongs to exactly one connected output; only a 2. Every attached workspace belongs to exactly one connected output; only a
workspace awaiting an output may contain `None`. workspace awaiting an output may contain `None`.
3. An output exposes exactly one active workspace. 3. An output exposes exactly one active workspace.
4. A window with `WindowPlacement::Tiled` appears exactly once in its workspace 4. A window with `WindowPlacement::Layout` appears exactly once in its active
tree and not in the floating list. workspace layout and not in the floating list.
5. A window with `WindowPlacement::Floating` appears exactly once in the 5. A window with `WindowPlacement::Floating` appears exactly once in the
floating list and not in the tree. floating list and not in the workspace layout.
6. Fullscreen does not alter the window's underlying placement membership. 6. Fullscreen does not alter the window's underlying placement membership.
7. A `FocusTarget::Window` refers to a managed window on 7. A `FocusTarget::Window` refers to a managed window on
`seat.focused_workspace`; a `FocusTarget::Shell` refers to a live, authorized `seat.focused_workspace`; a `FocusTarget::Shell` refers to a live, authorized
@@ -426,6 +426,80 @@ policy.
## 6. Layout ## 6. Layout
Each workspace has an independent layout mode. `Scrolling` is the default;
`Tree` is an optional traditional tiling mode. Changing modes MUST preserve
window membership, focus history, floating geometry, and fullscreen state.
```rust
pub enum WorkspaceLayout {
Scrolling(ScrollingLayout),
Tree(LayoutTree),
}
pub struct ScrollingLayout {
pub columns: Vec<Column>,
pub active_column: Option<usize>,
pub viewport_x: f64,
}
pub struct Column {
pub windows: Vec<WindowId>,
pub active: usize,
pub width: ColumnWidth,
pub heights: Vec<f32>,
}
pub enum ColumnWidth {
Proportion(f32),
Fixed(f32),
Full,
}
```
Scrolling layouts maintain these invariants after every transaction:
1. Empty columns do not exist, and every managed non-floating window appears in
exactly one column.
2. `active_column` is `None` exactly when the workspace has no columns;
otherwise it and every column's `active` index are in bounds.
3. Proportional and fixed widths are positive and finite. Height weights are
positive, finite, match the column's window count, and normalize
deterministically.
4. `viewport_x` is finite and clamped to the strip, while permitting the final
column to align with either viewport edge.
Scrolling columns form a horizontal strip that MAY be wider than the usable
output. Windows within a column divide its height. The viewport follows focus
only as far as required to reveal the focused window and SHOULD retain part of
an adjacent column when space permits. It MUST NOT recenter on every focus
change. An explicit center command MAY center the focused column.
Opening, closing, moving, or resizing a window MUST preserve a stable visual
anchor: columns before the focused column do not jump merely because a later
column changed. Each workspace stores and restores its own viewport. Output
resize or migration clamps that viewport without discarding it. Constraint
resolution MUST prefer the focused column, preserve user-selected widths where
possible, and report conflicts rather than silently compressing every column to
fit. These stable anchors, deterministic constraints, and retained neighboring
context are required Ferese behavior, not optional polish.
The first normal window creates the first column. A new normal window creates a
column after the focused column by default. Commands and drag targets can insert
before or after a column, move a window into an existing column, extract it into
a new column, cycle standard widths, set a precise width, or redistribute
heights within a column. Full-width columns occupy the viewport but do not alter
neighboring column widths.
Mode conversion is deterministic. Scrolling-to-tree conversion builds
horizontal structure from columns and vertical structure from windows within a
column, preserving reading order and size proportions. Tree-to-scrolling
conversion traverses visible leaves in left-to-right, top-to-bottom order and
initially creates one column per leaf. Conversion preserves membership and
focus, but users should not expect mode-specific grouping metadata to survive a
round trip.
Tree mode uses the following node model:
```rust ```rust
pub enum Node { pub enum Node {
Window(WindowId), Window(WindowId),
@@ -451,9 +525,10 @@ Split ratios MUST be finite and clamped so descendants satisfy their minimum
sizes where possible. If all constraints cannot be met, the focused window takes sizes where possible. If all constraints cannot be met, the focused window takes
priority and the conflict MUST be logged. priority and the conflict MUST be logged.
The engine consumes a layout tree, usable logical geometry, size constraints, The engine consumes a workspace layout, usable logical geometry, size
and tiling configuration. It returns a deterministic `WindowId -> RectF` map and constraints, and mode-specific configuration. It returns a deterministic
constraint warnings. Identical input MUST produce identical output. `WindowId -> RectF` map, viewport metadata, and constraint warnings. Identical
input MUST produce identical output.
The following tree invariants MUST hold after every layout transaction: The following tree invariants MUST hold after every layout transaction:
@@ -468,7 +543,7 @@ The following tree invariants MUST hold after every layout transaction:
6. The focused leaf inside a stack is the active child's recursively focused 6. The focused leaf inside a stack is the active child's recursively focused
window; directional operations enter a stack through its active child. window; directional operations enter a stack through its active child.
### 6.1 Automatic insertion ### 6.1 Tree-mode insertion
1. The first tiled window becomes the root. 1. The first tiled window becomes the root.
2. A later window splits the focused leaf. 2. A later window splits the focused leaf.
@@ -478,18 +553,21 @@ The following tree invariants MUST hold after every layout transaction:
### 6.2 Required operations ### 6.2 Required operations
Ferese MUST support directional focus, movement and resizing; explicit split Ferese MUST support directional focus, movement and resizing; scrolling-column
direction; floating and fullscreen toggles; tiled-divider resize; floating insertion, extraction, grouping, width cycling, and viewport centering; optional
move/resize; and tiled drag-and-drop. tree split direction and divider resize; floating and fullscreen toggles;
floating move/resize; and layout-aware drag-and-drop.
Drag targets are `left`, `right`, `top`, `bottom`, and `center`. A drag MUST show Drag targets are mode-specific and MUST show a live preview. In scrolling mode,
a live preview. Center creates or joins a stack; an edge creates a split. left/right inserts a column and top/bottom/center inserts into a column. In tree
mode, center creates or joins a stack and an edge creates a split.
### 6.3 Gaps ### 6.3 Gaps
Gaps belong exclusively to `[tiling]`. `inner_gap` separates tiles and Shared gap defaults live under `[layout]`; mode-specific behavior lives under
`outer_gap` separates tiles from output bounds. With `smart_gaps = true`, a lone `[scrolling]` and `[tiling]`. `inner_gap` separates managed windows and
tiled window has no outer gap. `outer_gap` separates the layout from output bounds. With `smart_gaps = true`,
a lone managed window has no outer gap.
## 7. Geometry and animation ## 7. Geometry and animation
@@ -799,7 +877,7 @@ workspace space.
Fullscreen is a presentation state over the preserved underlying workspace Fullscreen is a presentation state over the preserved underlying workspace
layout. Entering fullscreen MUST hide the Ferese top bar visually without layout. Entering fullscreen MUST hide the Ferese top bar visually without
changing the underlying workspace usable layout region. Leaving fullscreen MUST changing the underlying workspace usable layout region. Leaving fullscreen MUST
restore the bar without reconfiguring hidden tiled windows merely because the restore the bar without reconfiguring hidden managed windows merely because the
bar reappears. bar reappears.
A temporary top-bar reveal over fullscreen MUST use overlay presentation and A temporary top-bar reveal over fullscreen MUST use overlay presentation and
@@ -809,7 +887,7 @@ underlying workspace layout.
If `ferese-shell` disconnects unexpectedly, the compositor SHOULD retain the If `ferese-shell` disconnects unexpectedly, the compositor SHOULD retain the
last valid Ferese-owned top-bar reservation for a short bounded grace period. last valid Ferese-owned top-bar reservation for a short bounded grace period.
If the shell reconnects with a compatible reservation before the lease expires, If the shell reconnects with a compatible reservation before the lease expires,
tiled windows MUST NOT reflow. If the grace period expires, Ferese removes the managed windows MUST NOT reflow. If the grace period expires, Ferese removes the
reservation and performs at most one ordinary layout reflow. reservation and performs at most one ordinary layout reflow.
### 11.3 Private shell control protocol ### 11.3 Private shell control protocol
@@ -912,7 +990,9 @@ Configuration has one authoritative representation. Namespace ownership is:
[shell.*] shell behavior ferese-shell-design.md [shell.*] shell behavior ferese-shell-design.md
[animations] compositor animation policy this document [animations] compositor animation policy this document
[tiling] layout policy this document [layout] shared layout policy this document
[scrolling] scrolling layout policy this document
[tiling] optional tree-layout policy this document
[input] input policy this document [input] input policy this document
[commands] launch command vectors this document [commands] launch command vectors this document
[[bindings]] compositor bindings this document [[bindings]] compositor bindings this document
@@ -936,10 +1016,19 @@ stiffness = 700.0
damping = 53.0 damping = 53.0
mass = 1.0 mass = 1.0
[tiling] [layout]
mode = "scrolling"
inner_gap = 10.0 inner_gap = 10.0
outer_gap = 10.0 outer_gap = 10.0
smart_gaps = true smart_gaps = true
[scrolling]
default_column_width = 0.5
width_presets = [0.333333, 0.5, 0.666667, 1.0]
neighbor_context = 48.0
new_window = "column_after_focused"
[tiling]
default_split = "automatic" default_split = "automatic"
[input] [input]
@@ -1219,12 +1308,14 @@ animations. Performance tracing MUST be optional. Suggested targets are
### 17.1 Layout ### 17.1 Layout
Unit tests MUST cover insertion, deletion, focus, movement, ratios, constraints, Unit tests MUST cover scrolling-column insertion, extraction, grouping, focus,
resize, stacks, floating transitions, fullscreen placement preservation, viewport clamping and stable anchors; tree insertion, deletion, movement,
workspace migration and restoration, gaps, shared-edge rounding, and ratios, constraints, resize and stacks; floating transitions, fullscreen
deterministic geometry. They MUST NOT require Wayland. Property tests SHOULD placement preservation, workspace migration and restoration, gaps, shared-edge
prove arbitrary operation sequences preserve acyclic trees, valid parentage and rounding, layout-mode conversion, and deterministic geometry. They MUST NOT
stack cardinality, and do not duplicate or lose windows. require Wayland. Property tests SHOULD prove arbitrary operation sequences do
not duplicate or lose windows and preserve scrolling column membership or tree
acyclicity, valid parentage and stack cardinality as applicable.
### 17.2 Configuration ### 17.2 Configuration
@@ -1278,14 +1369,16 @@ Acceptance: text copies between native clients; drag-and-drop completes;
decorated and undecorated clients can move and resize; scaled hit testing matches decorated and undecorated clients can move and resize; scaled hit testing matches
rendered content; a user-initiated test launch receives a valid activation token. rendered content; a user-initiated test launch receives a valid activation token.
### M2: Tiling and workspaces ### M2: Layout and workspaces
Implement identifiers, layout trees, insertion, focus, movement, resize, gaps, Implement identifiers, default scrolling columns, optional layout trees,
floating, fullscreen, and workspaces. insertion, focus, movement, resize, gaps, floating, fullscreen, and workspaces.
Acceptance: five terminals tile without overlap; navigation works in all Acceptance: five terminals populate a horizontally scrollable column strip
directions; close reflows layout; modes restore correctly; randomized tests without overlap; focus reveals columns without unnecessary recentering; tree
preserve all invariants. mode remains selectable; navigation works in all directions; close reflows the
active layout; modes restore correctly; randomized tests preserve all
invariants.
### M3: Animated geometry ### M3: Animated geometry
@@ -1390,16 +1483,16 @@ resource growth; all Section 3.2 desktop-tier requirements pass.
```text ```text
nested Smithay compositor nested Smithay compositor
-> two xdg-shell terminals -> two xdg-shell terminals
-> deterministic two-window tiling -> deterministic two-column scrolling layout
-> logical/visual/client geometry separation -> logical/visual/client geometry separation
-> spring reflow with coalesced final configure -> spring reflow with coalesced final configure
``` ```
The slice succeeds when A and B tile automatically; closing B immediately updates The slice succeeds when A and B form columns automatically; closing B
the logical layout while A expands visually; A receives no per-frame configure immediately updates the logical layout while A expands visually; A receives no
flood; pointer input remains aligned through inverse transforms; shared edges per-frame configure flood; pointer input remains aligned through inverse
round identically; terminal changes damage the correct regions; and all transforms; shared edges round identically; terminal changes damage the correct
interaction remains responsive. regions; and all interaction remains responsive.
M4 follows this nested slice immediately so frame pacing is validated on real M4 follows this nested slice immediately so frame pacing is validated on real
hardware. Semantic materials, GPUI, Settings, XWayland, notifications, portals, and the hardware. Semantic materials, GPUI, Settings, XWayland, notifications, portals, and the
@@ -1412,7 +1505,8 @@ Ferese v0 is complete when a user can:
1. start a direct Ferese session; 1. start a direct Ferese session;
2. run ordinary native Wayland applications and layer-shell utilities; 2. run ordinary native Wayland applications and layer-shell utilities;
3. copy, paste, drag, and use application popups; 3. copy, paste, drag, and use application popups;
4. tile, focus, move, resize, float, fullscreen, and close windows; 4. scroll, focus, move, resize, group, float, fullscreen, and close windows,
with optional tree tiling;
5. use multiple workspaces and outputs; 5. use multiple workspaces and outputs;
6. configure input, bindings, rules, gaps, corners, borders, shadows, and 6. configure input, bindings, rules, gaps, corners, borders, shadows, and
animations through validated live reload; animations through validated live reload;
@@ -1423,7 +1517,8 @@ Ferese v0 is complete when a user can:
10. perform routine native-Wayland programming work without regular compositor 10. perform routine native-Wayland programming work without regular compositor
restarts. restarts.
The intended v0 result is a low-latency, independently usable tiling compositor. The intended v0 result is a low-latency, independently usable scrolling
compositor with optional tree tiling.
The desktop tier adds a coherent Ferese shell, semantic surface materials, The desktop tier adds a coherent Ferese shell, semantic surface materials,
graphical settings, graphical settings,
portals, and X11 compatibility without redefining core correctness. Visual portals, and X11 compatibility without redefining core correctness. Visual