# TextSpan Refactor The current design of `Text`, `Text2d` and `TextSpan` is a compromise that attempts to address several needs: * The vast majority of text entities are simple strings with no internal divisions or styles. It should be easy to spawn a simple text entity without having to create a complex layout structure. * At the same time, we also need to be able to represent more complex text entities consisting of multiple spans, possibly with different styles for each span. * Text can appear in various rendering contexts. Within a UI hierarchy, text is a `Node` which participates in layout, while in a 2d or 3d context it is displayed like a sprite. These different contexts require different components on the top-level text entity. ## Current Design * We use `Text` for UI contexts and `Text2d` for other contexts. * These two components have different required components. For example, `Text` has a required component of `Node`, while `Text2d` has a required component of `Anchor` (among others). * Both of these components can display text as a singleton without any children. * Both allow children in the form of `TextSpan` entities. * The text spans in the children are appended to the parent in a combined flow. So for example, the following snippet would display the string "One, two, three.": ```rust (( Text::new("One, "), children![ TextSpan::new("two, "), TextSpan::new("three."), ] )) ``` To get around the confusion of having text at multiple levels, Bevy provides the `TextWriter` API which builds this structure automatically. However, in practice, what a lot of users instead do is to simply make the top-level text node an empty dummy: ```rust (( Text::new(""), children![ TextSpan::new("One, "), TextSpan::new("two, "), TextSpan::new("three."), ] )) ``` ## Problems with this design * Because the UI text node is called simply `Text` rather than `UiText`, users may not realize that it can only be used in a UI context. * More broadly, to use text correctly, users need to know (a) whether the current context is a UI context or not, and (b) what flavor of text entity to use for the different contexts. * Having text strings at different levels of the hierarchy that are concatenated into a single contiguous flow is non-intuitive. * Some users have complained that `TextWriter` is confusing (Search for "TextWriter" on Discord). ## Proposal 1: Rename `Text` to `UiText`. This would make it clearer to users that they can't use the `Text` component in a 2d/3d context, but it doesn't help with the other issues. ## Proposal 3: Implicit text layout containers. Ickshonpe has proposed making text layout containers implicit. This means that you could write: ```rust (( Text::new("One, "), Text::new("two, "), Text::new("three."), )) ``` This is similar to the behavior of text nodes in HTML, where sibling nodes are automatically merged into a unified flow. In this proposal, there is no longer a `Text2d` component, as `Text` is used in all contexts. Instead, we would need to what type of layout to used based on whether the parent is a UI node or not. This raises the question: if there's no text root, then where does the layout information (such as `TextLayout`) live? Presumably it would be on the parent entity, or if there is no parent, then the individual text entities would be treated as separate roots. There is an [open PR that demonstrates this](https://github.com/bevyengine/bevy/pull/21496), although the code uses a somewhat brute-force algorithm that has not been optimized. This is to be treated as a proof of concept. ## Proposal 3: RichText component. The idea here (from Nico Burns) is instead of making `Text` do dual-duty as both a leaf and as a container, we introduce a separate `RichText` component that can only function as a container. ```rust (( Text::new("One, two, three."), )) ``` or ```rust (( RichTextUi, children!: [ TextSpan::new("One, "), TextSpan::new("two, "), TextSpan::new("three."), ] )) ``` This means that there would be four different kinds of root text components: * `RichTextUi` or possibly `UiRichText` * `RichText2d` * `TextUi` or possibly `UiText` * `Text2d` (Because a name in Rust can't begin with a digit, we can't have "2d" as a prefix, but only a suffix.) ## Proposal 4: Unified RichText. Finally, we can combine the idea of `RichText` with some kind of automatic context detection, so that the correct layout components are inserted based on inspecting the parent. In this proposal, you can either create a singleton text entity using `Text`, or a group of texts using `RichText`: ```rust (( Text::new("One, two, three."), )) ``` ```rust (( RichText, children!: [ TextSpan::new("One, "), TextSpan::new("two, "), TextSpan::new("three."), ] )) ``` As in proposal 2, there is no longer a distinction between `Text` and `Text2d`. Instead we detect the type of context by inspecting the parent, and insert the appropriate layout components to the text root as needed. Unlike the previous proposal, the layout components are stored on the text root, not the parent entity, although the determination of what kind of components are needed is still automatic. Note that if `RichText` is nested within another `RichText`, it creates a new layout context, much like an `inline-block` in CSS. In effect, what we are doing here is inventing a kind of "context-sensitive required components", that is, different sets of required components determined by the parent. To keep things simple, let's say that we punt on the question of tracking hierarchy changes - e.g. what happens if you move a text entity from a ui parent to a non-ui parent. This is justified by the fact that it's "no worse" than the situation today with required components. ## Proposal 5: Unifying UI and non-UI contexts completely. Both Ickshonpe and Nico Burns have advocated in favor of unifying 2d/3d contexts with UI more broadly. However, that's a much larger effort and out of scope of this document. ## Open Questions / Risks This proposal treats "UI context" as a binary choice: either the parent has a `Node` (which clearly signals that we're in a UI context), or it doesn't (in which case we assume it's a 2d or 3d context). We don't try to look for some 2d/3d component like `Transform` to identify a 2d/3d context because there's no single obvious component to use. However, this assumes that there won't be further bifurcation in the future into other kinds of context. In fact, the opposite assumption may be more likely: that UI and 2d/3d contexts will eventually be unified in some way.