gtk: improve split sizing#13414
Open
dkinzler wants to merge 3 commits into
Open
Conversation
Moved logic to sync split ratio between gtk.Paned widget and split tree into new syncSplitRatio function in SplitTreeSplit widget. For widget resizes the split ratio is now synced directly from the propMaxPosition callback instead of an idle callback. With this change all surfaces will be sized correctly from the start, in a single round of size allocation in GTK. Previously, surfaces would initially be shown with the wrong size for a few frames until the idle callback ran, resulting in visible flickering. This was especially visible when resizing a split quickly by holding down the resize keybind.
tristan957
reviewed
Jul 22, 2026
| } else { | ||
| // If only position changed, this is a manual human update and | ||
| // we need to write our update back to the tree. | ||
| // Update ratio of the split tree node. This is the most dangerous |
Member
There was a problem hiding this comment.
Should we add some debug asserts here?
Contributor
Author
There was a problem hiding this comment.
Can't hurt, added assertions for all the assumptions in the comment.
| // Sync split ratio between the gtk.Paned widget and the split tree. | ||
| // If to_paned=true, update the gtk.Paned widget to match the ratio | ||
| // in the split tree and vice-versa for to_paned=false. | ||
| fn syncSplitRatio(self: *Self, to_paned: bool) void { |
Member
There was a problem hiding this comment.
I don't love to_paned. Maybe a well named enum would be easier to understand when reading the code?
Contributor
Author
There was a problem hiding this comment.
Changed to use an enum, that's definitely better. Alternatively I also thought about just having a function that returns all the parameters (min, max, position, current_ratio etc.) and then doing the rest directly in propMaxPosition/onIdle, but that is also kind of awkward.
…Direction enum argument to specify whether we sync from widget to split tree or vice-versa. This make the code more readable. Added debug assertions to syncSplitRatio where we look up the split tree.
…get or data model cannot be found, we would continue running with a null pointer in builds without safety checks. Using unreachable was the wrong choice.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR improves the way splits/surfaces are sized in the GTK app, which eliminates flickering and slightly improves performance.
Fixes #13328, #12709, #11187.
Related #8208 (closed) but some later comments mention flickering issues persisting.
Builds on top of the changes in #12698.
Previously an idle callback was used to sync the split ratio between the GTK widget tree and the split tree data structure that represents the split layout. The widget tree contains a
SplitTreeSplitwidget, which wraps aGtkPanedwidget, for every split. During size allocation aGtkPanedwidget first computes the initial position of the divider and thereby the size for its two children. We get notified of that position (and the max possible position) via thepropPosition/propMaxPositioncallbacks inSplitTreeSplitand set up an idle callback (theonIdlefunction) to update the position if it does not match the desired split ratio. Since the initial position is often not correct, especially in nested layouts or if the ratio is not 0.5, a surface will first be shown with the wrong size for a few frames until the idle callback runs and corrects the sizing. In nested layouts it might take multiple rounds of size allocation and idle callbacks until every surface gets the correct size. This causes flickering as widgets eventually snap to another size, which is especially noticeable if the layout changes quickly e.g. when resizing a split using keybinds.To fix this, the divider position will now be corrected directly from the
propMaxPositioncallback, which runs during GTK size allocation, right after aGtkPanedcomputes the initial position and right before it uses the position to allocate sizes for its two children. With this change every surface will be sized correctly during the first round of size allocation.The idle callback is still used to update the ratio in the split tree when a split is resized by manually dragging the divider in the UI. The logic to sync the split ratio was moved to the new
syncSplitRatiofunction which is called from bothpropMaxPositionandonIdle.This is kind of hacky, but I reviewed the GTK source code in detail to verify that this is safe (see the various code comments for more details). I also tested extensively on both Hyprland and KDE Plasma: creating deeply nested layouts, resizing with both keybinds and dragging dividers by hand, with multiple tabs, resizing the entire window, resizing entire subtrees to 0 and back. Everything seems to work fine.
For performance testing I used sysprof which can also collect GTK stats. When creating/deleting/resizing splits I can measure a slight but consistent increase in GTK FPS (+5 to 10) on my system. Other than that CPU usage and FPS seem to be the same before and after. I guess this makes sense, while we added a bit of work to the GTK loop during size allocation, we avoid surfaces being resized.
For the flickering, here's a side-by-side comparison. Left is before the changes, right is after.
comparison.mp4
AI Disclosure: no AI was used.