-
-
Notifications
You must be signed in to change notification settings - Fork 32
Feat: dwindle layout #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat: dwindle layout #171
Conversation
First phase of Hyprland's dwindle layout Dwindle Improvements Polished soem features from previous commit and added some new features Deafult Config Added dwindle settings and default keybinds to the default config file
295dec3 to
aa311ae
Compare
|
In the end dwindle is also a binary tree, I've been playing with implementing this myself and figured it would make sense to refactor it to a point where there is a base tree layout and dwindle and bsp only implement what's really different. Right now this is a lot of copy paste between dwindle and bsp. If I'd have to maintain this I would want to de-duplicate this further but that's just my 5 cents. |
|
Hi @alexmohr 👋, You are right however the reason I did this is because I don't know if @acsandmann wants to accept this as default or have two separate layouts bsp and dwindle, I didn't want to assume and impose such changes without making sure, so if he wants to make dwindle default bsp it's very easy and if he wants them separate then I will make the refactoring |
|
i would be okay if there were two engines(bsp + dwindle) sharing base code. i personally don't use any bsp tiling algo so what is correct is really up to the users. just for a tech debt pov, i think two separate engines that share a lot of boilerplate thats stored in another file would be the cleanest solution. |
|
First Impression: Seems like it could be cool. One thing I notice is that when I open up your configuration file provided (no app rules or workspaces added) -- I have many windows open. Some may argue too many. Basically a lot of my windows end up stuck at the bottom in this layout and I am unsure how to get to them. After messing with it for some minutes: I can kind of see how it could be cool. I'm not sure which concept I like more.... Dwindle or scrolling. Should I have just moved windows to another space to test it? Sorry I was never a hardcore Linux tiling WM user so a lot of this is new and exciting to me, haha. I'd certainly like to see it fleshed out more and I appreciate your efforts. I just read your post. Sorry I am an idiot. I'm realizing this is like, a mouse oriented layout right? No wonder I wasn't getting it. Haha. I think I definitely need to add in my workspace / app rules to test it properly. In any case, I think i'm too tired to figure this out tonight. I'll try again tomorrow. |
|
@alexmohr I remember you having a lot of experience with the advanced Hyprland dwindle features. I’ve refactored dwindle with @acsandmann’s approval, and it would be great if you could test the features you normally use in Hyprland and let me know if anything needs adjustment. If you’d like to contribute as well, you’re more than welcome to ❤️ Since I’ve always used the default CachyOS Hyprland setup and never really experimented with its more advanced features, and because I currently don’t have any non-Mac hardware, I’d prefer not to go through the process of installing Asahi just to understand the UX behaviour with the advanced functionality. If everything looks good, would it be possible @acsandmann to merge this into main? That way I can start focusing on the GUI settings and the workspace bar. The settings will take some time and will require coordination with you to make sure they meet your expectations. |
Is this ready to test now? Also is this mouse oriented or keyboard oriented layout? |
i think its probably the same as the trad layout since its just a variant of bsp. i think it just instead of splitting it rotates and splits or something. atleast that is my understanding of it.. |
It seems like the windows "dwindle" down, hence the name dwindle. At least that is what it seemed like during my initial testing. It seemed highly dependent on the mouse though, instead of the window focused by Rift. Has this been changed in latest iteration? Personally I'm becoming old fart age and I am trying to avoid the mouse. (As I use the mouse to press "Update comment" 🤦 ) |
acsandmann
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these things don't 100% have to be changed but id like to hear rationales just so i better understand the changes. Also, i think there are some opportunities to leverage existing primitives and maybe decrease complexity a bit. looks pretty close to being mergeable though! thanks!
| "Alt + F" = "toggle_fullscreen" | ||
| "Alt + Shift + F" = "toggle_fullscreen_within_gaps" | ||
| "comb1 + Ctrl + Space" = "toggle_focus_floating" # briefly bring focus to floating window | ||
| "Alt + Shift + T" = "toggle_split" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe these should be commented by default as by default the traditional layout is enabled
| JoinWindow(Direction), | ||
| ToggleStack, | ||
| ToggleOrientation, | ||
| Preselect(Option<Direction>), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ive been trying to minimize the size of the layoutcommand surface so im curious if some of these could be condensed into preexisting commands for brevity. if not, im fine with the additions but ideally we would keep them to a minimum
| } | ||
|
|
||
| #[derive(Serialize, Deserialize)] | ||
| pub struct DwindleLayoutSystem { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like there is a lot of state here. ideally the layout systems are stateless or as stateless as possible so we should see if we can use existing primitives to clean this up. (Namely: there is already a lot of stuff in rift for detecting drags and im sure we could move some of the Drag stuff there to clean this up). see actors/drag_swap.rs
Hold, I royally f'd up, I went lazy and used Claude to generate a commit message based on changes and push it but it has a sudo flag and did some changes while I went for a smoke and squashed them so now not only does it crash the app but also I need to go through all the changes (squashed in one commit) and see what went wrong and have no way of looking at commit history of my local changes. Sorry give me hopefully a few minutes and not a few hours. |
ca73e16 to
c0a767c
Compare
|
@BarutSRB Seems to work pretty swell compared to the other iteration I attempted to use. Although I don't know all of the functionality of dwindle so I am likely not testing the "full experience" I will test more later tonight with a more fleshed out configuration file. I'll try to follow your instructions for testing too. I like how you can have a window sized and swap it out with smaller windows though. Having never used dwindle before, I lost the terminals that I started Rift in, but was able to figure out how to get to them using the arrow keys. Some feedback from my first impression:
|
|
@brianwk sorry for not answering earlier and thank you for taking your time to test. Hyprland’s dwindle layout is a BSPWM like layout where every tiled window on a workspace is part of a binary tree. Each internal node represents a split and each leaf is a window. Splits are not permanent by default. For any parent container Hyprland chooses the split orientation dynamically from its aspect ratio: if the container is wider than it is tall the children are arranged side by side, and if it is taller than wide they are stacked top and bottom. You can lock a given split orientation by enabling preserve_split, or override the automatic choice with options like force_split and smart_split. force_split can force every new split to the left or right (or top or bottom), while smart_split looks at which conceptual triangle of the window your cursor is in and uses that to pick the split direction, and it also enables preserve_split.  When you open a new window in a dwindle workspace Hyprland inserts it by splitting an existing node in that tree. Which container gets split is controlled by use_active_for_splits, which decides whether dwindle prefers the currently focused window or the window under the mouse when choosing where to attach the new client, and by the force_split setting that controls whether splits follow the mouse or are forced to one side. By default, dwindle is configured to prefer the active window and to choose orientation from the aspect ratio rules, but you can make it feel much more mouse driven by changing use_active_for_splits and enabling smart_split. Dwindle also exposes mouse move and resize directly via main_mod + left mouse and main_mod + right mouse, and provides layout specific dispatchers such as layoutmsg togglesplit, layoutmsg swapsplit and layoutmsg preselect for keyboard control of the same tree structure. Focus and navigation operate on that binary tree rather than on a simple grid, so directional movement and swapping can feel different from the master layout, and if you keep adding leaves without reorganizing them into other workspaces the visible tiles naturally dwindle in size as the tree deepens. |
testable commit - toggle split working, toggle swap working, promote to root master working, smart resize working, needs testing for these features to see if they are smooth for everyone and if possible to test other 9+ features or combinations of as dwindle is pretty complex layotu with many features.
|
@brianwk I think I got to a somewhat testable state, if you are goign to test please look at the attached config. Here is also a video of soem of its features in action, liek toggle split, swap, promote to root/master, smart resizing which uses mouse position to figure out in which direction to size the focused window etc... full screen toggle works naturally ofcrs, later the pseudfo tiling will need to be worked on in order to have certain windows always fit and so that the layout observes them in order not to overflow certain windows that have minimum or maximum sizes. Demo video of some features of dwindle: |
unrelated, but how did you disable the window title bar or is that just a feature of the terminal you're using? |
|
It's a ghostty terminal feature |
👋 I will test this latest change soon. But I am confused, sorry.... what layout is better for smaller screen: scrolling or dwindle? In Dwindle can I have a larger Zed window and a bunch of smaller ones that I can swap in and out? For example, I have small Zed Windows on the side, and when I swap with the larger Zed Window it will basically swap sizes when I swap it. Hopefully makes sense what I said! Currently if I move a window it will forget it's size in Rift. Not sure if it's intentional or a bug! My hope is that we can have a layout in Rift as I described. I feel like I spend my entire life resizing windows and it drives me insane! Plus it makes me feel like a grandpa! |
|
@brianwk you can pretty much do anything you wish with dwindle that's the beauty of it. It's a Swiss Army knife basically and you were asking for to have one large window and then smaller ones is basically selecting a window and the pushing it to root command, which I think I believe in my config is alt shift M. But it's also the hardest layout to get on parity when porting. |
Okay I'm gonna give it it a try. Is it up to date with latest main branch? |
|
It should be but it also needed changes to some stuff especially the gaps horizontal and vertical are not enough for proper dwindle layout to function so I had to add top bottom left right to it so you might get config parsing error. Keep in mind I'm trying to get as close to parity of Hyprland's dwindle as technically possible due to macOS constraints and given around 14 features that it should have will take some time and fine tuning to get it to work, and from what I know there hasn't been a full proper port of it for windows or macOS yet and I might not even be able to do it, but I'd appreciate any contributions |
yeah it should be a relatively trivial thing to do to implement per workspace layout engines. i just haven't thought of a nice way to expose it via config/cli. and yes all the commands are events and then the layout engines handle them in their own ways. |
There are still some bugs in the layout. I will focus on layout specific bugs since I think some of the issues I mentioned earlier in the thread may be Rift issues, not necessarily issues in his layout implementation. I'd really love to see some type of visual indicator showing where the new window will appear. Rift seems to have some focus issues with certa
I think some of the issues I mentioned here are affecting Rift as a whole. I likely use Rift wildly different than most people since I don't move around windows too much other than to another display when I plug it in. So using this layout made me realize some other bugs when switching from this PR back to rift main branch. |
Resizing 2 for testing
…#178) Applied fix acsandmann#178 for resize-grow and resize-shrink functionality inverted
|
@acsandmann I've been testing the user behavior parity of Hyprland's dwindle and Rift's dwindle layout and they pretty much behave exactly the same from user perspective, the only thing that is missing to do is resizing the way Hyprland does it. Are you okay with adding a new resizeactive command (Hyprland style pixel/percent, exact mode) alongside the existing ratio based resize commands, with original implementation limited to tiling (floating unchanged)? Reason being it’s a user visible surface change and could affect expected behavior/maintenance policy confirming scope and floating window expectations avoids surprises. As the current one won't produce the same behavior for dwindle layout. |
that already exists
"Alt + Shift + Equal" = { resize_window_by = { amount = 0.05 } }0.05 being a % |
It isn't equivelant, would you prefer for me to do it and then you can say yes or not or even better once I get it fully working on parity then for you to refactor/modify/edit however you want the whole feature? Would that be easier? |
yeah i mean you probably could make resize_window_by take an enum of different ways to resize. im pretty sure resize_window_by isnt really used by anyone so im open to some breaking changes. maybe make these changes in a separate pr since they affect other layout engines and it would be better for bookkeeping. |
|
@acsandmann Thank you will do, I think by tomorrow I will be having it on parity with Hyprland and ready for official testing so far I tested creating and removing new windows, swap, split, move window, focus navigation, promote to master window and all behave exactly like Hyprland which I'm happy I wasn't expecting that kind of parity. Once done feel free to refactor/restructure, modify etc as you are far better then me at coding. Thank you once again. |
… resizing Replace the old percentage-based resize commands (resize-grow, resize-shrink, resize-by) with a comprehensive Hyprland-compatible resizeactive command that supports pixel/percentage values, corner specification, and exact/relative modes. Key enhancements: - New resize module with ResizeCorner, ResizeMode, ResizeValue, and ResizeDelta types - CLI command accepts x/y parameters with pixels or percentages (e.g., "50", "10%") - Optional corner specification (topleft/tl, topright/tr, bottomleft/bl, bottomright/br) - Optional exact mode for absolute sizing vs relative adjustments - Smart corner detection from cursor position when corner not specified - Boundary checking prevents resizing windows stuck to screen edges - Position tracking in layout engine for accurate resize calculations - Updated all three layout systems (BSP, Dwindle, Traditional) with new resize logic Technical changes: - Added src/layout_engine/resize.rs with comprehensive type system - Replaced LayoutCommand variants: ResizeWindowGrow/Shrink/By → ResizeActive - Added layout_positions HashMap to track window rectangles - BSP: Corner-aware split ratio adjustments with find_parent_split helper - Dwindle: Smart resizing with inner split compensation via apply_resize_axis - Traditional: Directional resizing based on corner and delta sign - Updated LayoutSystem trait signature for resize_active method This provides a more intuitive and flexible window resizing experience that aligns with modern tiling window manager conventions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Resizing 1
resizing 2 - fix smart resizing off
|
@acsandmann Ready for your disecting and refactoring/modifying 🫡😂❤️ |
|
it seems to work fine there are just some code quality nits. there are a lot of comments in function bodies that i think we could get rid of. also, im not really sold on the hyperland style gaps. it not only further complicates the code but i don't know if i want it.. also, i think the way the resize command is expressed in the config could be cleaned up since it is kind of overly complex (imo). good job though, this is really close. appreciate the pr. i would've said this in a pr review but github keeps crashing.... |
Thank you, yeah I honestly didn't like the resize command either, granted it's more precise but it is overly complicated however I just couldn't get it to behave same as in Hyprland without rewriting some already existing things or reinventing the whole dwindle, but I do agree with you. Comments are there by AI as I don't ever put them instead once done I ask AI to write them in as I'm to lazy 🙈 Im open to any changes if it works, as I'd like to move to scroll/niri layout and finish that up so that you can release two layouts for v0.3.0 I was also thinking later to focus on the GUI settings tou mentioned on your road map and possibly add to app rules minimum width/height for certain apps so that layouts know not to try and shtink them down further to avoid overlaps as some apps just don't obey. |
|
@BarutSRB It's working really good now. The only thing I noticed is that the grow / shrink is inverted. If I switch the hotkeys around it won't be inverted (which I totally did) but yeah it's inverted. Looking forward to the scrolling layout updates. |
|
Hi @brianwk sorry for not responding sooner was busy. Yeah, one of the big problems with macOS is unlike Linux WMs we don't have custom compositor or full access to Quartz so we can't draw the size of an app and some apps don't respect the size imposed, the way to get around this on macOS is to implement a system where a user can make a list of apps/windows with specified minimum/maximum height/width so that tiling can know how much space to reserve for certain apps so they don't overlap or go off screen. I also have major problems with some apps. Move to root outs the windows at top of the tree for that workspace making it biggest window something like how Master layout functions. Why you don't see these problems with tiles or accordion is because they split the windows for example (tiles) into 3 columns which still gives enough space for apps not to overlap but if you try to put more problematic apps into one workspace it will still break it visually including Aerospace tiles. These are the hurdles of macOS tiling WMs 😫 Theoretically it's possible to find an exploit to get passed SIP and get more control of Quartz but that would be a huge multi team work and pointless as Apple would quickly patch it up as it would present a security concern, so best we can do is be as creative as possible with available APIs |
|
I came across this today, @BarutSRB : https://www.reddit.com/r/wayland/comments/1pg939f/i_built_a_native_macos_wayland_compositor_over/ Let's hope it's all real. |
I don't get it. It looks like he has made virtually no progress. |
looks like pointless ai slop.. |
|
It is pure AI to the point where it makes no sense what he is even saying in the comments. It seems more like he would prompt the AI based on comments and the AI would hallucinate and give him some new plan that's why his plan changed 100 times in that thread. There is 0 chance you can make a compositor for Mac in a few weeks. First you'd need to reverse and find a way to inject that compositor and rewrite every visual interaction you currently see in macOS. Heck, even Asahi Linux project is having problems now imagine writing a macOS compositor |






https://www.youtube.com/watch?v=iPxYIq73Z2o
When testign please try to use this config config.txt
Here are available config options
[settings.layout.dwindle]
default_split_ratio = 1.0 # allowed: 0.1..1.9
split_width_multiplier = 1.0 # ratio heuristic: width > height * multiplier => horizontal split
smart_split = false # use cursor quadrant to decide orientation/direction
preserve_split = false # keep existing split orientation unless overridden
force_split = 0 # 0 follow heuristic, 1 force first (left/top), 2 force second (right/bottom)
split_bias = true # bias split toward new window (mirrors Hypr's split_bias)
use_active_for_splits = false # prefer selection over cursor
permanent_direction_override = false # keep preselect after one insert
smart_resizing = true # adjust ratios based on cursor extent when resizing
pseudotile = true # keep floating size when tiled
single_window_aspect_ratio = [16.0, 9.0] # enforce aspect when only one window; set second to 0 to disable
single_window_aspect_ratio_tolerance = 0.05 # fractional padding before aspect coercion
How to test each feature:
cursor to a specific side of that leaf’s area, then open a new window; orientation/direction should follow the cursor quadrant. If you want clearer
cursor-driven behavior, temporarily set use_active_for_splits = false and reload.
parent (even if the child’s aspect would suggest flipping). Toggle preserve_split off if you want to see it flip based on aspect.
ratio). Without bias, it would be even. Compare by toggling split_bias off/on.
heuristics and always place accordingly.
permanent_direction_override = false, the preselect clears after one use.
Its ancestor subtree should be promoted to the root’s primary side without flipping sides (stable=true).
matching split along that axis should adjust; moving the cursor to another edge changes which split moves.
at that size centered within its slot instead of filling it. Resizing a tiled window (if the system delivers resize events) will update the stored
pseudo size.
Add a second window; aspect enforcement stops.
Keybindings for dwindle actions: