-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Add Display::Content layout type #9731
Comments
Simple example of why this is useful (if I understand it right): We implement a game menu: We save a game. (I realise now that this menu doesn't really make sense, just suppose that pressing "New Game" creates a save file) This adds a new "Load Save 3" button to the menu: The menu's implementation inserts the new child at an index calculated from the previous number of "Load Save" buttons plus the number of other buttons before the "Load Save" buttons in the list of children. This is fragile and complicated, so we wrap the "Load Save" buttons inside a single child node instead: Now we don't have to worry about indices. Instead, the "Load Save" button nodes are added as children to This is okay but now we're bothered about the empty space at the bottom of the menu. We decide that we want to divide the space up evenly inbetween the buttons, so we set
However, if we had a |
I'm not 100% sure but I think I could implement this in Bevy without too much difficulty or requiring Taffy support. Style updates would need to be modified to walk the layout tree instead of iterating through a style query maybe. |
Your analysis is 100% correct. I realize that the example of a save menu is a bit artificial, but you do find this pattern in React/Solid code a lot where a list of items is rendered at the same level as other items. Let me take the time to describe how you would handle these cases without implementing Before taking this on, I would suggest checking to make sure that this is something that the new UI framework is actually going to use. |
So, I'm surprised and pleased that you actually implemented this so quickly. I have a few more notes.
#[derive(Debug, PartialEq, Clone)]
pub(crate) enum TemplateOutput {
// Means that nothing was rendered. This can represent either an initial state
// before the first render, or a conditional render operation.
Empty,
// Template rendered a single node
Node(Entity),
// Template rendered a fragment or a list of nodes.
Fragment(Box<[TemplateOutput]>),
}
impl TemplateOutput {
/// Flattens the list of entities into a vector.
fn flatten(&self, out: &mut Vec<Entity>) { ... }
} Once a view element has called the render function of its children, it has a vector of So the question for me now is whether to keep my workaround, or adopt the |
So, as I've continued in my UI research, I've determined that I no longer need this feature; this problem can be solved at a higher level. So if you want to close this, feel free. |
What problem does this solve or what need does it fill?
As we move towards a more reactive UI framework, there is a question of how to support conditional rendering and "foreach" nodes.
By conditional rendering, I mean a subtree of the UI that either exists or does not exist based on some condition, such as a modal dialog, a game mode, a tab panel and so on. Generally speaking, when a UI mode is not visible, you don't want to simply "hide" the elements, you want them to not exist at all - because the existence of these nodes often has side-effects beyond simply their impact on the layout.
Similarly, it is often the case that you'll have an array of items that are rendered from some data source that is an iterator, such as a list of saved games.
Immediate-mode frameworks have no problem with either of these patterns. However, a UI that retains elements wants to preserve the node tree where possible, otherwise you lose state (things like cursor position, text selection, focus and accessibility cues). This means that we want to avoid re-generating nodes when they have not changed. There are various strategies for this: the "React" approach is to generate a new list of child entities and compare that list with the old one; The "Solid" approach is to remember each child's index relative to its parent, and then "patch" the list as needed.
However, conditional and iterative UI elements are a challenge because they change the size of the child entity list, and alter all of the indices, making both comparing and patching difficult. For example, if I have a UI node whose children consist of a list of saved games followed by a "save" button, then the list index of that save button is going to change when additional save games are added. This is a challenge for both the React and Solid approaches.
For conditional nodes, a dummy "placeholder" node can be used to take up a child slot when the child is not being rendered. However, you then need to inform the layout engine to ignore this node in layout calculations. For lists, it's more complex, because a simple placeholder won't work.
What solution would you like?
The solution to all these problems is
Display::Content
. This is an idea that is relatively new in CSS, and is not supported in all browsers yet, but makes sense in the context of Bevy UI. The basic idea is simple: a node withDisplay::Content
is handled by the layout engine as if it were replaced by its children. That is, the children of the element are hoisted up one level by the layout engine and rendered in place of that element. If the element withDisplay::Content
has no children, then it is simply ignored.For conditional rendering, we create a placeholder node that represents the condition itself, and its single child is actually the conditionally-rendered content (or no child if the condition is not enabled). For iterative rendering, the placeholder node has a variable number of children depending on the number of elements in the array. In both cases, it means we can re-evaluate and re-generate the children of the conditional node, without altering the list indices of any of its siblings.
What alternative(s) have you considered?
The alternative (that I can think of) is to keep track of child node indices as a separate data structure, but this requires a lot more bookkeeping.
The text was updated successfully, but these errors were encountered: