Replies: 8 comments 9 replies
-
|
My core criteria here:
Overall, this proposal meets my requirement and is well reasoned and explained! I prefer the distinct components over a monolithic I think I'd prefer using
Agreed: that's the strongest argument against it. I would also really like to not make |
Beta Was this translation helpful? Give feedback.
-
With required components it's necessary to have a dedicated type for pulling in components for the root node. |
Beta Was this translation helpful? Give feedback.
-
|
Looking for sign-off from the following contributors before attempting an implementation:
|
Beta Was this translation helpful? Give feedback.
-
|
Intuitively entity-per-text-section has always seemed the right direction, my worry with this design is that it could be very awkward to manage the text sections after they've been spawned (unless you just despawn the whole text tree and spawn a new one every time you make a change). Like from the example: Suppose that you want to change the text to only display the word "World". Ideally you'd just want to be able to delete the "Hello, " text entity. But it's the head of the tree so that would break the text hierarchy. You could despawn the "World" entity, and change the "Hello, " string to "World" but then you also have to remember to update the font, and you have to understand the structure of the text tree and locate the world-text entity. And that's only a very trivial construction. It's not very clear to me how to make the ergonomics as simple as with a vec of sections. |
Beta Was this translation helpful? Give feedback.
-
I'm thinking the proliferation of components might be overly complicated. I propose we merge TextLayout into TextBlock for conceptual simplicity.
Sadly
I'm pretty strongly in opposition to this, as it makes this all needlessly complicated. It essentially makes a "one to many" constructor required for reasonable ergonomics, which I consider to be unexpected / unidiomatic in the new scene system / a missed ergonomic opportunity. I believe this approach came from a desire to share I do like your proposal to make the "cosmic data" / buffer component explicitly encoded via required components, rather than implicitly computing what needs it at runtime. I propose the following, which I think will make this all fit more pleasantly in peoples' heads: #[derive(Component)]
pub struct TextStyle {
pub font: Handle<Font>,
pub size: f32,
pub color: Color,
}
/// This is always an (empty by default) "final destination" for text. It is where you "put" spans of text,
/// it is not a span of text itself.
#[derive(Component)]
#[requires(TextLayoutInfo, ComputedTextBlock)]
pub struct TextBlock
{
pub justify: JustifyText,
pub line_break: LineBreak,
}
#[derive(Component)]
pub struct ComputedTextBlock {
pub(crate) buffer: CosmicBuffer,
pub(crate) entities: SmallVec<[TextEntity; 1]>,
pub(crate) needs_recompute: bool,
}
/// This is the top-level UI text component. If a string is specified, it behaves as if it has a "first" TextSpan child.
#[derive(Component)]
#[require(TextBlock, TextStyle, Node, ContentSize, TextNodeFlags)]
pub struct Text(String);
/// This is the top-level 2D text component. If a string is specified, it behaves as if it has a "first" TextSpan child
#[derive(Component)]
#[require(TextBlock, TextStyle)]
pub struct Text2d(String);
/// This will contribute its text to the parent TextBlock (or ancestor in a TextSpan tree leading to a TextBlock)
#[derive(Component)]
#[require(TextStyle)]
pub struct TextSpan(String);
// 1.a Simple UI text node.
//
// Node, Buffer ["Hello"]
commands.spawn(Text("Hello"));
bsn! {
Text("Hello")
}
// 1.b Simple 2d text node.
//
// Buffer ["Hello"]
commands.spawn(Text2d("Hello"));
bsn! {
Text2d("Hello")
}
// 2.a UI multi-span text.
//
// Node, Buffer ["What a ", "wonderful ", "world"]
commands
.spawn(Text("What a "))
.with_children(|parent| {
parent.spawn(TextSpan("wonderful "));
parent.spawn(TextSpan("world"));
});
bsn! {
Text("Hello") [
TextSpan("wonderful "),
TextSpan("world"),
]
}
// 2.b 2d multi-span text.
//
// Buffer ["What a ", "wonderful ", "world"]
commands
.spawn(Text2d("What a "))
.with_children(|parent| {
parent.spawn(TextSpan("wonderful "));
parent.spawn(TextSpan("world"));
});
bsn! {
Text2d("What a ") [
TextSpan("wonderful "),
TextSpan("world"),
]
}
// 2.c Simple multi-span UI text
//
// Text, Node, Buffer ["Hello, ", "World"]
commands
.spawn(Text::default())
.with_children(|parent| {
parent.spawn(TextSpan("Hello, "));
parent.spawn(TextSpan("World, "));
});
bsn! {
Text [
TextSpan("Hello, "),
TextSpan("World, "),
]
}
// 2.d UI multi-span text expanded from markup "[b]Hello, [i]World[\i][\b]"
//
// Text, Node, Buffer ["Hello, ", "World"]
commands
.spawn(Text::default())
.with_children(|parent| {
parent
.spawn((
TextSpan("Hello, "),
TextStyle {
font: fonts.get(FontFamily::new("Fira Sans").bold(),
..default()
})
));
parent
.spawn(TextSpan::default())
.with_children(|parent| {
parent.spawn((
TextSpan("World"),
TextStyle {
font: fonts.get(FontFamily::new("Fira Sans").italic())
},
));
});
bsn! {
Text [
(TextSpan("Hello, "), TextStyle { font: {fonts.get(FontFamily::new("Fira Sans").bold()} })
TextSpan [
(TextSpan("World"), TextStyle { font: {fonts.get(FontFamily::new("Fira Sans").italic()} }
]
]
}
// 3. Multi-node UI
//
// Node
// - Node, Buffer ["Hello"]
// - Node, Buffer ["World"]
commands
.spawn(Node)
.with_children(|parent| {
parent.spawn(Text("Hello"));
parent.spawn(Text("World"));
});
bsn! {
Node [
Text("Hello"),
Text("World"),
]
}
// 4. Invalid, no TextBlock. Warning emitted.
commands
.spawn(TextSpan("Hello"))
.with_child(|parent| {
parent.spawn(TextSpan("World"));
});
bsn! {
TextSpan("Hello") [
TextSpan("World")
]
}
// 5. Valid: This is a normal text node with a child Text node. They are each their own TextBlock
commands
.spawn(Text("Hello"))
.with_child(|parent| {
parent.spawn(Text("World"));
});
bsn! {
Text("Hello") [
Text("World")
]
} |
Beta Was this translation helpful? Give feedback.
-
|
This PR for |
Beta Was this translation helpful? Give feedback.
-
|
Implemented in #15591 |
Beta Was this translation helpful? Give feedback.
-
|
Seeing how we have |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Continued from the
Lorum Ipsumdiscord working group. This is a proposal for a new text API using required components and replacingVec<TextSection>with a hierarchy of text entities.See cart's comment for the current proposed design. Most of the discussion below remains relevant.
Objectives
Textinto separate components to take advantage of the new required components API. Deprecate existing text bundles.bevy_mod_bbcodeuses a custom multi-entity solution for text.Background
Right now we use
cosmic-textto control text layout. We feedcosmic-textan iterator over text sections, and it constructs aBufferthat knows how to organize the text contents. The buffer is then used to construct aTextLayoutInfowhich has all the glyphs and their positions for rendering text in Bevy (both UI and 2d text).When doing UI layout with
taffy, a node with text on it is treated as having an opaque 'content block' (see theContentSizecomponent). Whenever a node has a content block,taffyasks the block for its computed size using different x/y bounds as part of thetaffylayout algorithm (CSS grid/flexbox). For a text node, you compute that size by using the text'sBufferto recompute text layout.Summary
Currently we store text sections as
Vec<TextSection>in a singleTextcomponent that has all text layout and style information.In this proposal, we break
Textinto a tree of text nodes. At the root of the tree are the following components:TextLayout: controls wrapping/line breakingTextBlock: contains the cosmic text buffer and caches the list of text section entitiesTextSpan: indicates the node should be tracked as part of text tree collectionAny node of the tree underneath a
TextBlock(including the entity withTextBlock) can include the following components (if a child doesn't haveTextSpanthen we don't traverse into that child - only contiguousTextSpannodes are considered):Text: contains a stringTextStyle: font, font size, colorTextSpan: indicates the node should be tracked as part of text tree collectionTo delineate between UI and 2d we have:
TextNode: marker component for UI text, requires components for setting up a UI nodeText2d: marker component for 2d text, requires components for setting up a 2d nodeThe final bit of magic to make this ergonomic is using
TextNode::new()andText2d::new()to produce bundles instead of making just the components. This introduces a small learning curve but makes everything fit together nicely.API
Implementation details
TextorTextStylechanges on entity, iterate upwards to findTextBlockthen set itsneeds_updateflag. This flag is used bytext2dand UI text to decide if the buffer should be rebuilt.TextPipeline::update_buffer, instead of taking in a&[TextSection], take inimpl Iterator<Item = (&Text, &TextStyle)>. This iterator should internally traverse the entity hierarchy and rebuild theTextBlock::entitiesvector in-line. This is an effective way to keep the entities vector accurate without any extra hierarchy traversal/management code.TextLayoutInfo. The section index can be used withTextBlock::entitiesto look up the correct entity for picking events.OnRemoveobserver forTextthat sets theTextBlock::needs_updateflag on the nearestTextBlockancestor.CosmicBufferis embedded inTextBlockand should not be edited by users since any edits will be overwritten by the internal systems that filter forTextBlock. However, buffer construction,ContentSizemeasurements, andTextLayoutInfoassembly will be opt-in via theTextBlock,TextNode, andText2dcomponents. A user can instead manually construct and updateContentSizeandTextLayoutInfoby adding a custom component/code for managingcosmic-textbuffers, and simply not including those opt-in components. TheTextPipelineshould be refactored a bit to facilitate this (split upTextPipeline::queue_textso you can constructTextLayoutInfowithout needing bevy-specific text sections/blocks).Node,ContentSize,TextLayoutInfo,CustomCosmicBuffer+ code to manage theseSpatial2d,TextLayoutInfo,CustomCosmicBuffer+ code to manage theseOpen questions
taffy,Visibility, andTransformtraversal, we want to ignore the childTextentities. One solution would be aSkipChildrenmarker component required byTextBlock. Or perhapsSkipChildren<T>and then requireSkipChildren<Node>,SkipChildren<Visibility>,SkipChildren<Transform>.Alternatives
Display::Flowsetting to the UIStylewhich would have behavior similar toTextBlock.Textcomponent, and auto-detecting the root node of text blocks (possibly withDisplay::Flowplaying a part).Problems with the alternatives:
Textneed to be aware of each other for proper auto-setup (e.g. 2d and UI).Display::Flowis also desirable for 2d.Texton all the nodes of a text block. This isn't a hard blocker to the alternatives since they could useTextSpanlike I do in this proposal.Display::Flowwould increase the complexity ofStyle, which is already quite overly-complected.Beta Was this translation helpful? Give feedback.
All reactions