feat(scroll): add consume/expel window commands (with gating/compat alignment)#151
feat(scroll): add consume/expel window commands (with gating/compat alignment)#151BarutSRB wants to merge 18 commits intotmandry:mainfrom
Conversation
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 tmandry#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 tmandry#129.
Remove the ConsumeIntoColumn and ExpelFromColumn commands in favor of using MoveNode, as suggested in PR tmandry#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 tmandry#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.
This comment was marked as off-topic.
This comment was marked as off-topic.
|
@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. |
|
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:
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. |
|
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. |
|
I'll get on this tonight or tomorrow ❤️ |
|
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. |
|
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 |
|
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. |
|
@tmandry Hey sorry for being so absent, I did rewrite and then i noticed that the problem was in architecture so I went back to swift and it fixed it, but then users started following and making issues, to the point where I couldnt drink coffee as I wanted to reduce the number off issues (OCD syndrom) I still want to merge the projects, but for me biggest blocker is rust, as I truly cant understand it jsut liek C++ or Cobalt, so I have to use AI for PR and merges, and I do treust you as you have shown multiple times your knwoldge and skill, but it jsut frustrates me that I see lines of code by AI but I dont understand it in rust, so I feel usless, also i tested small parts in rust, zig and swift, and some ended up a lot better in p95 and p99 tests especially AX APis which shocked me. Why I'm writing this is because I don't know if I will have the will to keep up with everything as the job which is not IT is drainign me and the conomy is not good so that also takes a tool on me, if you are ok, I would love to be able to send PRs or explanations hwo somethign was done (for example Ghostty tabs which even Rift copied) and for you to do the polishing, as the monthly claude eats a lot of tokens and I have to use it for Rust as I dont know rust, and end up spending most of its monthly limits asking it to explain why something was done in such a way or for it to confirm in plain words line by line changes so that I can see if that is what I wanted. The good news is that I have achieved for Niri layout perfect parity with the original including aniamtions timings, so I can not with confidence start pushing stuff via AI and than if something is wrong jsut compare it with Omni to see what is wrong, as before this wasnt possible as Omni itself has bugs. Let me know if you are ok with taking the lead and polishing so that I can start pushing stuff, as for the previous PR I ended up asking so many times AI to explain how something is wrong because im not sure if its jsut me but I find Rust/C++ alien to me, while c and zig i find relaxing if that makes sence and swift well i know it so... If you are ok, Id start first with native tabs fix creating ghost windows, all it woudl take is toi change so it doesnt only watch for ghostty but applies it to all apps that trigger natiuve tabs. Than iud push smart expel/consume And work on polishing niri layout After this id work on swift but not swiftui as it showed in omni to be buggy for support on multipel versions of macos but instead appkit, and make a drop down menu and analyze the current config to sketch it in figma to feel intutiver and organize it, once that is done, later its easy to add extra tabs in setting sfor new configs etc... Only problem is that for Niri to feel like the original it requires hiding on the sides other windows as it makes it least distance traveled and no resizing, and this presents a problem for multi monitor support, but i was thinginkg which is kind of a bloat, to make a virtual fake monitor where we can hide windows, so they dont leak, I tested this and it worked perfectly for hiding but it also crashed a lot, which casn always be fixed but I just did it to see if it woudl work and it did, however for some reason even though the monitor was fake, macos was still rendering it and treating it as visible and I would love to find a way to avoid that so we can get leaner resource hog |
Summary
Add scroll-layout
consume_or_expel_windowcommands (left/right) to move the focused window into/out of adjacent columns.What this adds
consume_or_expel_window = "left" | "right".Included compatibility/alignment fixes
While integrating this feature, this PR also includes small safety alignments so behavior stays correct:
experimental.scroll.enable = false.scroll_rootson load to preserve expected scroll-tree behavior.Files
glide.default.tomlsrc/actor/layout.rssrc/config.rssrc/model/layout_tree.rsVerification
Ran and passed locally:
cargo test consume_or_expel_window -- --nocapturecargo test rebuild_scroll_roots -- --nocapturecargo test -q scroll_wheel_is_ignored_when_scroll_gate_disabled -- --nocapturecargo test -q scroll -- --nocapture(28 passed)cargo test -q change_layout_kind -- --nocapture(1 passed)CI will run full
fmt/check/build/teston the PR.Notes
Feature remains behind existing experimental scroll gating.