Skip to content

feat(scroll): add consume/expel window commands (with gating/compat alignment)#151

Open
BarutSRB wants to merge 18 commits intoglide-wm:mainfrom
BarutSRB:feat/scroll-consume-expel
Open

feat(scroll): add consume/expel window commands (with gating/compat alignment)#151
BarutSRB wants to merge 18 commits intoglide-wm:mainfrom
BarutSRB:feat/scroll-consume-expel

Conversation

@BarutSRB
Copy link
Contributor

Summary

Add scroll-layout consume_or_expel_window commands (left/right) to move the focused window into/out of adjacent columns.

What this adds

  • New layout command: consume_or_expel_window = "left" | "right".
  • Keybinding support for the command in config parsing.
  • Scroll-aware behavior in layout operations for consume/expel workflows.

Included compatibility/alignment fixes

While integrating this feature, this PR also includes small safety alignments so behavior stays correct:

  • Scroll-only commands remain no-op when experimental.scroll.enable = false.
  • Legacy restored layouts backfill scroll_roots on load to preserve expected scroll-tree behavior.

Files

  • glide.default.toml
  • src/actor/layout.rs
  • src/config.rs
  • src/model/layout_tree.rs

Verification

Ran and passed locally:

  • cargo test consume_or_expel_window -- --nocapture
  • cargo test rebuild_scroll_roots -- --nocapture
  • cargo test -q scroll_wheel_is_ignored_when_scroll_gate_disabled -- --nocapture
  • cargo test -q scroll -- --nocapture (28 passed)
  • cargo test -q change_layout_kind -- --nocapture (1 passed)

CI will run full fmt/check/build/test on the PR.

Notes

Feature remains behind existing experimental scroll gating.

Barut and others added 18 commits February 2, 2026 11:15
Niri Layout
# Conflicts:
#	src/config.rs
- Fix animation timer: use Timer::manual() with guard to only fire while animating
- Restore removed doc comments flagged in review
- Apply if-let chain suggestion in reactor.rs
- Use ScreenInfo struct per review suggestion
- Document scroll settings individually in glide.default.toml
- Add TODOs for deferred review items (configurable modifier, animation in model)
- Run cargo fmt to fix CI style failures
Unify the viewport and non-viewport code paths for interactive window
move hit-testing into a single loop. Previously the logic was duplicated
inside an `if let Some(vp)` branch and its `else` branch.

Addresses Gemini review feedback on PR glide-wm#129.
Extract the common layout-application logic from `update_layout` and
`update_layout_no_anim` into a shared `apply_layout` function.
A `LayoutMode` enum distinguishes between animated and immediate
window placement, eliminating the near-complete duplication between
the two methods.

Addresses Gemini review feedback on PR glide-wm#129.
Remove the ConsumeIntoColumn and ExpelFromColumn commands in favor of
using MoveNode, as suggested in PR glide-wm#129 review. Also restore comments
that were inadvertently removed across layout.rs, mouse.rs, reactor.rs,
and layout_tree.rs.

Addresses tmandry's review feedback on PR glide-wm#129.
solve_sizes enforces a 50px minimum window size, which is appropriate
for scroll layouts but breaks tree layouts with small test screens
(e.g. 100x100 with 2 windows where one should be 40px after resize).
Use 1.0 as the minimum for tree layouts to preserve existing behavior.

Also removes a debug eprintln accidentally left in the resize handler.
apply_viewport_to_frames referenced crate::actor::app::WindowId,
violating the model→actor layering rule. Make the function generic
over the ID type since it only passes the value through.
SpringAnimation and ViewportState called Instant::now() directly,
coupling the model layer to wall-clock time and forcing tests to use
thread::sleep. Accept Instant as a parameter throughout the model API
so callers in the actor layer provide the timestamp.
set_config unconditionally overwrote every scroll column's weight,
discarding widths set via CycleColumnWidth or interactive resize.
New columns already get their weight from visible_columns at creation
time — existing columns shouldn't be touched.
For windows smaller than 2*RESIZE_EDGE_THRESHOLD, detect_edges could
set both LEFT+RIGHT or TOP+BOTTOM simultaneously, causing conflicting
resize deltas that cancel out. Clear both edges on an axis when the
window is too small to distinguish them.
scroll_sensitivity was not validated, allowing extreme values to
produce billions of traversal steps and hang the reactor. Clamp
sensitivity to [0, 100], filter invalid column width presets, and
cap step count to 16 as defense-in-depth.
Put scroll layout behind an experimental gate and default it to off.

This adds and enforces settings.experimental.scroll.enable as the runtime gate. When the gate is off, scroll behavior is disabled: ignore scroll wheel handling for scroll layout, no-op scroll-only commands (change_layout_kind, toggle_column_tabbed, cycle_column_width), prevent default_layout_kind=scroll from activating and fall back to tree, convert active scroll layouts back to tree when the gate is disabled, and skip scroll layouts during next/prev layout cycling when gated off.

Default config was updated so scroll commands are not bound by default, with opt-in examples for users who enable the experimental gate. Added tests for gate defaults and gate-off behavior.
@gemini-code-assist

This comment was marked as off-topic.

gemini-code-assist[bot]

This comment was marked as off-topic.

@BarutSRB
Copy link
Contributor Author

@tmandry you have to try this new command. It might just be me, but I think you’ll like it. The only problem is that niri is still very buggy with more than one monitor.

@tmandry
Copy link
Contributor

tmandry commented Feb 28, 2026

So this is close to what MoveNode does in the tree layout, except it "auto creates" a column if one doesn't exist in the consume case.

Do you imagine this command doing the same in the tree layout case?

I think this would generalize in all directions... basically if you are moving left/right it should create a column or stack; if moving up/down it would create a horizontal or tabbed node.

Continuing down that path, there are basically two kinds of moves that could each be their own commands:

  1. Swap a node with its sibling
  2. Move into/out of container (this command)

If a tree layout has an invariant that each level of nesting has an alternating direction, we can distinguish between a node and its parent based on the direction. This is usually the way I use it anyway, and it points to a possible simplification in the commands used to build layouts; only "split" and "group" are needed, along with these two kinds of move commands.

@BarutSRB
Copy link
Contributor Author

I really love your idea of making it geenralized adopting to the layout kind and the arrow directions, this would potentially lower the commands the user needs to memorize and freeing up keys for potentially other stuff or lowering conflicts with other apps.

@BarutSRB
Copy link
Contributor Author

BarutSRB commented Feb 28, 2026

I'll get on this tonight or tomorrow ❤️

@tmandry
Copy link
Contributor

tmandry commented Mar 5, 2026

We can merge the current behavior after a rebase. I do want to generalize but a follow-up PR works.

When using this I noticed that the tabs use left/right navigation but I think stacks would be more natural, what do you think? Then you can make use of both directions and avoid cycling through all tabs in a group when using the keyboard to navigate.

@BarutSRB
Copy link
Contributor Author

BarutSRB commented Mar 5, 2026

We can merge the current behavior after a rebase. I do want to generalize but a follow-up PR works.

When using this I noticed that the tabs use left/right navigation but I think stacks would be more natural, what do you think? Then you can make use of both directions and avoid cycling through all tabs in a group when using the keyboard to navigate.

If it's the cycle command prev/next it is by design going through all the windows in order as some like to use the least amount of hot keys but we can make it ignore if column tabbed on cycle and just move to next column instead of switching tabbed windows when cycling. If it's the left/right horizontal navigation than the tabbed windows should be up/down when trying to switch them. But if you have a better idea to make the least amount of if/else logics for those commands I'm up for it.

@BarutSRB
Copy link
Contributor Author

BarutSRB commented Mar 5, 2026

But you are right I think having cycle on tabbed columns left/right should move to next column and have an if for tabbed columns on cycle command for up/down

@tmandry
Copy link
Contributor

tmandry commented Mar 5, 2026

I'm using move focus left/right commands. I think spatial commands are an important basis for how Glide is used. If people aren't familiar with vim style directions, perhaps they should use arrow keys or WASD instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants