Repository navigation
Add a dock tree data model to bevy_ui_widgets #26034
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
Open
jbuehler23
wants to merge
5
commits into
bevyengine:main
Choose a base branch
from
jbuehler23:jackdaw/dock-tree
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,050
−0
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
3d568cc
Add bevy_ui_dock with a dock tree data model
jbuehler23 af5050a
Add PR number to release note
jbuehler23 dc1ab22
address feedback
jbuehler23 0b43d15
Fix typos
jbuehler23 78a3285
Explain the dock tree lifecycle in its docs
jbuehler23 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| --- | ||
| title: Dock tree | ||
| authors: ["@jbuehler23"] | ||
| pull_requests: [26034] | ||
| --- | ||
|
|
||
| `bevy_ui_widgets` can now describe dockable layouts as plain data. A `DockTree` component holds tab groups and the splits between them, with operations to add, move, split and close tabs. Empty groups are removed and nested splits are merged as the tree changes. | ||
|
|
||
| Split sizes are flex weights, the same as `Pane::size` in the split pane widget. Each tab names its content with a `DockPanelKey` that the app maps to its own UI, and with the `serialize` feature a layout can be saved and restored. | ||
|
|
||
| ```rust | ||
| let mut tree = DockTree::new(); | ||
| let center = tree.root(); | ||
| tree.add_tab(center, "viewport")?; | ||
| tree.split(center, DockEdge::Left, "outliner")?; | ||
| tree.split(center, DockEdge::Bottom, "assets")?; | ||
| ``` | ||
|
|
||
| The dock tree holds the data only. Building UI from the tree and dragging tabs between groups come in later releases. |
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| //! Dock layouts for Bevy UI, described as plain data. | ||
| //! | ||
| //! A [`DockTree`] describes how a region of the screen is divided into tab groups. It holds | ||
| //! no entities and draws nothing; UI is built from it and kept in sync by other code. Layout | ||
| //! changes such as moving, splitting or closing tabs are made on the tree. | ||
| //! | ||
| //! The tree has two kinds of node: | ||
| //! | ||
| //! - a leaf, [`DockLeaf`], is a tab group: an ordered list of [`DockTab`]s and the one that | ||
| //! is active | ||
| //! - a split, [`DockSplit`], lays out two or more children along a [`ControlOrientation`], | ||
| //! each with a flex weight that matches [`Pane::size`] | ||
| //! | ||
| //! Each tab names its content with a [`DockPanelKey`]. The app decides what content a key | ||
| //! stands for, so the same tree can be saved and loaded across runs. | ||
| //! | ||
| //! [`DockNodeId`]s of leaves and [`DockTabId`]s are stable across edits and never reused. | ||
| //! Split nodes are created and removed as the tree is simplified: empty leaves are removed, | ||
| //! splits with a single child are replaced by that child, and a split nested in a split of | ||
| //! the same orientation is merged into its parent. | ||
| //! | ||
| //! [`DockTree`] is a component, so an app can have several docks, one per root entity. With | ||
| //! the `serialize` feature it can be saved and loaded with serde. | ||
| //! | ||
| //! The tree is the source of truth for the layout, and the dock's UI entities are derived from | ||
| //! it. Widgets such as tabs and splitters only propose changes, which are applied to the tree, | ||
| //! and the UI is then brought back in line with it. Keeping the layout as plain data makes | ||
| //! operations like splitting at an edge or collapsing empty groups simple to write and test, | ||
| //! makes layouts easy to save and restore, and lets the UI entities be rebuilt without losing | ||
| //! the layout. | ||
| //! | ||
| //! ``` | ||
| //! use bevy_ui_widgets::{DockEdge, DockTree}; | ||
| //! | ||
| //! let mut tree = DockTree::new(); | ||
| //! let center = tree.root(); | ||
| //! tree.add_tab(center, "viewport").unwrap(); | ||
| //! tree.split(center, DockEdge::Left, "outliner").unwrap(); | ||
| //! let (right, _) = tree.split(center, DockEdge::Right, "inspector").unwrap(); | ||
| //! tree.add_tab(right, "settings").unwrap(); | ||
| //! tree.split(center, DockEdge::Bottom, "assets").unwrap(); | ||
| //! | ||
| //! assert_eq!(tree.leaves().count(), 4); | ||
| //! ``` | ||
| //! | ||
| //! [`ControlOrientation`]: crate::ControlOrientation | ||
| //! [`Pane::size`]: crate::Pane::size | ||
|
|
||
| mod tree; | ||
|
|
||
| pub use tree::*; | ||
Oops, something went wrong.
Oops, something went wrong.
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.
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.
Is this meant to be a representational source of truth, or a wire format derived from the scene?
Reading further down, I see that it is a component. What entity owns it?
I'm a little fuzzy on the lifecycle - if this is derived from the scene, it makes sense, since extracting this would make it easier to serialize. If OTOH the scene is derived from this, I'm not as clear why the scene isn't the source of truth (I'm sure there's a reason, I just don't know it yet.)
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.
Good question! The
DockTreeis the source of truth, and the UI is derived from it. It's a component on the dock's root entity, and the follow-up PR adds a reconciler that builds the split panes, tab lists and panel content as children of that entity, and keeps them in sync whenever the tree changes. Tabs and splitters only propose changes (same controlled pattern as the other widgets), and those get applied to the tree.The reason it isn't the scene itself is that the layout operations are tree operations on plain data. Splitting a group at an edge, moving a tab between groups and collapsing empty groups are much simpler and easier to test there than as entity hierarchy surgery. It also makes layouts easy to save and restore, and lets the UI entities be rebuilt or reused freely without losing the layout. Happy to talk through it more!
Uh oh!
There was an error while loading. Please reload this page.
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.
Can we get a comment that explains this?
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.
Added it to the module docs and the
DockTreedocs, explaining that the tree is the source of truth and the UI is derived from it.