Skip to content

FixedNode#24323

Merged
mockersf merged 30 commits into
bevyengine:mainfrom
ickshonpe:FixedNode
May 21, 2026
Merged

FixedNode#24323
mockersf merged 30 commits into
bevyengine:mainfrom
ickshonpe:FixedNode

Conversation

@ickshonpe
Copy link
Copy Markdown
Contributor

@ickshonpe ickshonpe commented May 16, 2026

Objective

Allow UI elements to be positioned relative to the viewport rather than their parent element.

Fixes #9564

Solution

  • New UI marker component FixedNode. Requires Node and OverrideClip.
  • FixedNode entities are treated as UI roots in layout, even if they have a parent.
  • FixedNodes don't inherit their parent's layout, clipping or transform context.
  • During the taffy layout updates children with FixedNode are skipped.
  • Added a couple of basic helper functions to UiSurface, mainly there to make the tests a little less painful.
  • Added a fairly comprehensive range of new tests, including tests with GhostNodes.
  • In the Taffy layout (stored in UiSurface) there is nothing to distinguish FixedNodes and root nodes, so they are treated identically during updates.

--

The original suggestion was to implement it as a PositionType::Fixed variant that could used with Node, but I think that would be much more complicated without support from Taffy. Being able to just directly query for and filter out FixedNode entities directly makes the implementation much simpler and more efficient.

Testing

Basic example which shows events bubbling up to the parent from the fixed node:

cargo run --example fixed_node

There are also a number of new tests in the layout module.

cargo test -p bevy_ui --lib --features ghost_nodes

@ickshonpe ickshonpe added A-UI Graphical user interfaces, styles, layouts, and widgets D-Straightforward Simple bug fixes and API improvements, docs, test and examples labels May 16, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in UI May 16, 2026
@ickshonpe ickshonpe requested a review from viridia May 16, 2026 21:41
Copy link
Copy Markdown
Contributor

@viridia viridia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release note please :)

@amtep
Copy link
Copy Markdown
Contributor

amtep commented May 16, 2026

What should happen if a root node has FixedNode? I think right now the code makes the assumption that the sets are disjoint.

@ickshonpe ickshonpe added the M-Release-Note Work that should be called out in the blog due to impact label May 16, 2026
@github-actions
Copy link
Copy Markdown
Contributor

It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note.

Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes.

@ickshonpe
Copy link
Copy Markdown
Contributor Author

What should happen if a root node has FixedNode? I think right now the code makes the assumption that the sets are disjoint.

Hopefully just adding a Without<Children> filter to the FixedNode queries will be sufficient. GhostNodes might cause some problems though. I'll add some comprehensive tests tomorrow.

@ickshonpe ickshonpe added the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label May 18, 2026
@viridia
Copy link
Copy Markdown
Contributor

viridia commented May 18, 2026

A minor comment on naming: I said in the issue that "position: fixed" was the best name choice because that's what it is called in CSS. However, if we're making this a separate component (which I assume is because it's hard to filter on otherwise) instead of a new Position variant, then using the word "fixed" by itself my not be the clearest choice. Other suggestions that have been discussed in the ticket include things like WindowRelative.

That being said, I honestly don't care too strongly about what it's called. I expect that this feature is mostly going to get used internally by widget libraries and no so much by game devs.

@ickshonpe
Copy link
Copy Markdown
Contributor Author

A minor comment on naming: I said in the issue that "position: fixed" was the best name choice because that's what it is called in CSS. However, if we're making this a separate component (which I assume is because it's hard to filter on otherwise) instead of a new Position variant, then using the word "fixed" by itself my not be the clearest choice. Other suggestions that have been discussed in the ticket include things like WindowRelative.

That being said, I honestly don't care too strongly about what it's called. I expect that this feature is mostly going to get used internally by widget libraries and no so much by game devs.

Yes I'm not very sure about the name either. The good things about FixedNode are that it's short and easy to remember, but I don't think the word "fixed" captures the right intuition. WindowRelative seems worse though, it's less catchy and not all render targets are going to be windows. Same with the other suggestions I've seen so far.

@ickshonpe
Copy link
Copy Markdown
Contributor Author

PinnedNode?

@ickshonpe
Copy link
Copy Markdown
Contributor Author

ickshonpe commented May 18, 2026

Another question is, should this also clear clipping? The reason it doesn't atm is that there's already the OverrideClip component, so preserving clipping is more flexible. But I'm not really sure if there's any situation where you'd need that flexibility and it'd probably match users intuition better if FixedNode requires OverrideClip.

@amtep
Copy link
Copy Markdown
Contributor

amtep commented May 18, 2026

RootNode because it acts as one?
FakeRootNode because it isn't really one?
PostitNode because it sticks to the screen?

Btw, is there still a difference between PositionType absolute and relative for these nodes?

@viridia
Copy link
Copy Markdown
Contributor

viridia commented May 18, 2026

Another question is, should this also clear clipping? The reason it doesn't atm is that there's already the OverrideClip component, so preserving clipping is more flexible. But I'm not really sure if there's any situation where you'd need that flexibility and it'd probably match users intuition better if FixedNode requires OverrideClip.

I agree with you on both points. I don't have anything to add, except this: trying to replicate the behavior of CSS entirely is a fool's quest: Bevy's ECS is simply too different from HTML and the DOM to be able to perfectly emulate all conceivable use cases. It's more sensible, I think, to focus on the specific use cases that we care about.

The primary use case is modal dialog boxes.

In an alternate timeline, we might have used this feature for dropdown menus, context menus, and tooltips, however we ended up going down a different route and instead used anchor-relative positioning with clipping turned off using your OverrideClip feature. Even in this latter use case we need to calculate the window-relative coordinates for positioning, but that is also doable using computed nodes.

Similarly, there's another timeline where we used FixedNode for notifications and toasts, but instead we've instead chosen to encode the toast content in a command or message and owned by a toast orchestrator entity which is a root.

Most things that want window-relative positioning (such as HUD elements or FPS diagnostic displays) are already root UI elements and don't need this feature. So the set of use cases is relatively narrow. It rests on the assumption that we will structure our UIs similar to how portals work in React: that a dialog box which appears visually centered on the screen can be "owned" by some minor UI component that is deeply nested within the main UI hierarchy. An "are you sure?" prompt can be spawned as a child of "logout" button, for example.

Yes, it's possible to do that without fixed nodes: you could mandate that any dialog box or prompt had to be a root node. But this makes communication and ownership more complex - a tiny child component that wants to ask the user "are you sure?" now needs to manipulate entities at the top of the hierarchy. This includes ensuring that the dialog box goes away if the owner does prematurely. Being able to have the dialog box be a child, but appear as a root, offers a simpler DX.

@ickshonpe
Copy link
Copy Markdown
Contributor Author

ickshonpe commented May 19, 2026

OverrideClip is required on FixedNode now.

It might be worth considering consolidating all these overrides into a single component. So have a single NodeOverrides component which allows you to selectively ignore/reset a range of inherited properties, layout, clipping, scrolling, scaling, rotation, translation, etc. That could make the API easier to understand and discover. Marker components compose better maybe though.

@ickshonpe ickshonpe added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 19, 2026
@amtep
Copy link
Copy Markdown
Contributor

amtep commented May 19, 2026

It might be worth considering consolidating all these overrides into a single component. So have a single NodeOverrides component which allows you to selectively ignore/reset a range of inherited properties, layout, clipping, scrolling, scaling, rotation, translation, etc. That could make the API easier to understand and discover. Marker components compose better maybe though.

The universe of coding is characterized by the eternal war between mergers and splitters. "We have a bunch of similar things, let's merge them!" "This thing is too big, let's split it!"

Sometimes the merger and the splitter are the same person. Tragedy ensues.

Comment thread crates/bevy_ui/src/layout/ui_surface.rs Outdated
Comment thread crates/bevy_ui/src/layout/ui_surface.rs Outdated
Comment thread examples/ui/layout/fixed_node.rs Outdated
@@ -0,0 +1,49 @@
//! Demonstrates UI pointer event bubbling.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad copy paste

Comment thread examples/ui/layout/fixed_node.rs Outdated
Co-authored-by: François Mockers <francois.mockers@vleue.com>
@mockersf mockersf enabled auto-merge May 21, 2026 09:04
Comment thread examples/ui/layout/fixed_node.rs Outdated
@mockersf mockersf added this pull request to the merge queue May 21, 2026
Merged via the queue into bevyengine:main with commit 6dca76a May 21, 2026
42 checks passed
@github-project-automation github-project-automation Bot moved this from Needs SME Triage to Done in UI May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-UI Graphical user interfaces, styles, layouts, and widgets D-Straightforward Simple bug fixes and API improvements, docs, test and examples M-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Add PositionType::Fixed

4 participants