#Lorem Ipsum: A New Text ~~Backend~~ Frontend for Bevy

1 messages · Page 2 of 1

tame aspen
#

Yeah my personal opinion is that unless there's really clear benefits I'd like to map one entity to one block of rich text

#

Reparenting everything when you bold a word seems miserable

prisma hemlock
#

Well supposedly that would be handled by some sort of reactive system

#

And it depends on how it's implemented. In html you have to have a separate element like <b>my bold text</b> to create bold text

#

Because a block of text can only have one style.

robust solstice
#

does the html pattern make sense with queries? In an ECS, you kind of want components to be something you can operate upon in a big block.

#

I have a difficult time seeing myself ever reaching for a query of all the bold spans in my app.

#

we can have markup elements that do not become structural ecs features

prisma hemlock
#

But what if you want to do fancier stuff like add marker components to specific pieces of text? Like how you can query css classes and id's

robust solstice
#

I'm not saying there shouldn't be a way to turn a block of text (or a sub-block or a sub-sub-block) into an entity. I'm just asking if it should be required to format stuff.

#

If we build a general rich text renderer and we have an orthogonal entity hierarchy for laying out of blocks of rich text, then you could implement a component that makes a block bold but you would not have to use that component to produce bold text.

tame aspen
#

And while I'm excited about BSN and reactivity and better tools for working with hierarchies, I want the low level to be nice too

#

The most granular unit in the ECS (a single entity) should correspond to something relatively stable and useful, and I think that "rich text section" lines up really well for things like swapping out text or localization

#

I used to think that spans as entities was better, but I've come around on it. Performance is very secondary for me here, and I think with a markup-style syntax the current architecture could be really pleasant to author, modify and hook into tools for writers

prisma hemlock
#

And a markup syntax can still compile down to entities

#

And only using entities for "stable" use-cases leaves a lot of interesting use-cases on the table.

#

Personally I think of an entity as a database entry, and a TextSpans table makes sense to me.

tawny oasis
#

I think at the very least having text span entities doesn't provide much benefit to end users by itself. Querying a block of rich text is a lot more useful here. But the representation also needs to play well with more advanced features. Can we easily introduce custom effects for spans, including animated ones, if it's part of a rich text block for example?

twilit iron
#

And the editor aspect of building a continuous selection of text for example

prisma hemlock
#

And selection across blocks. As a user, if I see two paragraphs of text my expectation is I should be able to select both even if they're separate blocks

prisma hemlock
#

Also what about things like on-click, on-hover, etc. handlers?

robust solstice
#

I think maybe we are thinking too much about UI as it is done on websites, rather than our actual immediate use-case: Games and The Editor.

#

Text selection is important; browser style text selection of static text that spans across multiple blocks probably isn't.

#

Blender doesn't really support this as far as I can tell.

prisma hemlock
#

When I was thinking about links I was thinking about crusader kings

robust solstice
#

Links sure, we want links probably. But cross block selection, maybe not. When was the last time you tried to select multiple paragraphs of text in a game?

tawny oasis
#

Yea we'll 100% need the ability to had something like links. Dialog systems might need them, and a lot of help infos link trough to other sections

tawny oasis
prisma hemlock
robust solstice
#

I'm cool with whatever feature list we want to come up with, and if you can find a use of something in a game or 3d modeling software, I think we should support it. But I don't want to fall into the bottomless pit of doing whatever the web does.

prisma hemlock
#

And I'm not sure that's the best line of reasoning. Lots of games don't have spatial audio so should we drop it?

#

I never mentioned web though (except the bold example but the point wasn't to say we should copy web)

#

And I really don't think it's helpful to immediately associate features with web and shoot them down

robust solstice
#

I just can't remember the last time I selected text outside of a text-entry box in a game.

#

Ditto for a 3d editor.

#

I associate the ability to just select big random swaths of rendered text strongly with the web and html.

zenith gull
robust solstice
#

To be clear, I am not trying to shoot down your idea: I am trying to apply a test. "Can I imagine a use for this in one of our target applications?" Currently my answer is no, and maybe I was a little harsh in the way I expressed that. I'm open to examples.

#

I guess multiplayer chatboxes often require text selection.

tawny oasis
#

On the web it would be a much bigger issue since you split up paragraphs into <p> blocks 🤔

tawny oasis
zenith gull
#

debug consoles need good text selection and copy imo

#

else we're going back to println! everything

#

which kinda defeats the point of an ingame debug console in the first place

robust solstice
tawny oasis
#

I feel like item lore would all be 1 block. But yea we'd at the very least need constraints for what can and can't be selected

#

Text selection becomes very difficult when multiple blocks are involved. I'm sure everyone has experienced the absolute backwards text selection logic PDFs throw at you sometimes 😂

robust solstice
#

Usually you have a big box with a jumble of text content inside and very little else. Being able to select labels and non-content text is imo an anti-pattern of the web and pdfs.

prisma hemlock
#

I think that's a fair stopping point as far as scope.

tulip hinge
# prisma hemlock And it depends on how it's implemented. In html you have to have a separate elem...

more importantly html has <a href="/">link<a/>. i'm motivated by both the ability to mutate individual sections (consider the FPS counter in some of our examples), as well as attaching arbitrary behaviours (focus, on click, hyperlinks), both of which would be made possible through components. spans in html are very flexible. ecs is even more flexible... if you use it. maybe it's a case of "when all you have is an ecs, everything looks like a system".

tawny oasis
#

maybe it's a case of "when all you have is an ecs, everything looks like a system".
Until someone unironically suggests Letters as Entities I don't think we're there yet thonk

tulip hinge
# tame aspen Reparenting everything when you bold a word seems miserable

i think that it would be up to the creator to know where they would be bolding or unbolding text, and that would be its own span.
{"FPS: "}{"60",bold?}

if you were implementing an editor it would be different of course, but in an editor you're not operating directly on the "text tree" provided by bevy, you're operating on a string or rope buffer or some sort of rich text tree. and that generates the bevy "view" text tree. I think the simplest approach would just be to do some diffing and patching in that case.

robust solstice
#

tbh this depends strongly on our reactivity story. If we ship a built-in solution for fearless ECS reactivity, then I'm fine to have the entire formatting tree expressed as entities. If not, and currently we are still at the prototyping stage, then using the entity hierarchy for everything is less compelling.

tame aspen
#

Yeah, until that solution is in hand I don't want to lean on reactivity too heavily

#

And maybe not even then

#

Being able to peel back the abstraction is nice and things shouldn't be horrible when you do so

tulip hinge
#

and a way to get the rectangles

prisma hemlock
#

If the low-level api is “horrible” then I think the move would be to introduce helper methods instead of restricting the capabilities of the system just for that use-case.

tulip hinge
#

also @white monolith handles selection and copy-paste etc. in bevy_cosmic_edit, so we can learn from that

prisma hemlock
#

But this is theoretical since we don’t have the new bsn proposal yet

#

I’m curious what Construct’s are and what we can do with them. It might make the non-bsn api better by offering a way to easily turn data into entities

tulip hinge
#

i have a feeling that bsn can lower to spans-as-entities much easier than special-casing text

tawny oasis
tulip hinge
#

also something comes to mind. it's not strictly true that cosmic-text keeps one string allocation, and attrs+indices.

it stores each line as a string (since it's optimised for an editor use-case).

buffer -> [bufferline(string, [attr]) -> [visual line]]

which is to say that even if we went in the opposite direction per @crude tinsel suggestion (one string, styles+ranges), we'd still have to allocate, especially since we're not sharing allocations. and then we're also having to implement a lot of helper behaviour to be able to mutate text like
{"Frame rate: ", const}{"60", mut}{" fps", const}

tulip hinge
tawny oasis
#

I think it would be different buffers here

tulip hinge
#

it's up to us to send the "selection events" to cosmic-text, so if they're in different buffers it just means we're starting in the middle of one buffer and finishing in the middle of the next... which looked at differently is the middle of buffer A until the end, and the start of buffer B until the middle

#

browsers probably have some cursed algorithm we could borrow from

#

also side note, PDFs are the worst format ever invented

tawny oasis
#

Browsers do have some different UI layout logic than we do, so it might not be easy to borrow

tulip hinge
tawny oasis
tulip hinge
#

i was shocked when i learnt that many PDFs store text as vector paths, and PDF readers largely just OCR the paths to give you something you can interact with as text

crude tinsel
#

Text selection across different text contexts is a pretty hard problem I think

#

Esp. If you combine with text in different orientations, rtl, etc.

tulip hinge
finite magnet
wooden drum
tawny oasis
#

A rich text system with localization support probably needs a first party option (as well as of course being replacable), making people search trough the third party options just makes people not localize their game/app or port it over later (which is generally not fun and a source of many non-localized things)

#

I'm also not sure how much of an opinion war there would be on this ... We're a community that uses discord and Rust, everyone is familiar with using markdown for all basic rich text needs ... Kind of similar to how godot has bbcode for their rich text, and the GDScript docs also use bbcode 🤔

twilit iron
#

I mean, if anyone wants the spans as entites, That would be nice to try out in a third party crate I assume. Basically just boil those entites down to the spans inside bevy

#

But imho, it feels quite close to "every char" as an entity

zenith gull
#

excuse my ignorance, but how is a span defined anyway?

twilit iron
#

and regarding the rich text section, markdown seems like a natural choice. Im not advocating against limiting other implementations , but I do believe that having a recommended way inside bevy is a good starting point.

every app or game has a lot of things it needs to do, so giving a default answer for most things is quite a good thing in getting a large project off the ground. Then also providing mechanics for going into the details and fixing/exchanging parts when they need to work in a specific way

tulip hinge
#

A couple issues with Markdown as the "frontend" for bevy_text:

  1. Markdown is too limited to be effective for the *overwhelming *majority of rich text scenarios. It is designed as an authoring format for documentation. As an alternative, something like asciidoc would be better, or some other format that lets you attach arbitrary attributes to spans and blocks. custom bbcode could work as well. XML or HTML (or something that maps to it) would be better.

  2. (As someone who just spent a couple hours trying to implement a markdown widget in bevy_ui) a Markdown snippet doesn't translate to a Text widget... it translates to a "Document", and from its original definition a HTML document: it is a nested format with blocks, including images, tables, list elements, etc. So for "inline attributes", you would be limited to: bold, emphasis... and that's about it.

tulip hinge
# zenith gull excuse my ignorance, but how is a `span` defined anyway?

A span is

In bevy_text:

(Component)
Text {
    sections: [TextSection { // one TextSection == one span
        value: String,
        style: TextStyle {
            font: _,
            font_size: _,
            color: _,
        }
    }]
}

It gets translated to cosmic-text:

Buffer {
    lines: [BufferLine {
        text: String,
        attrs_list: AttrsList {
            spans: RangeMap<usize, AttrsOwned> // AttrsOwned is roughly equivalent to bevy_text::TextStyle
        }
    }]
}
zenith gull
#

so it's a piece of text with constant attributes for all characters in it

tulip hinge
#

yeah in bevy text a span is just string with an associated style

wooden drum
# tulip hinge A couple issues with Markdown as the "frontend" for bevy_text: 1. Markdown is t...

Agreed, markdown seems fairly limited for our use cases. Colored text would be an important one that's not built into markdown but common in games.

I definitely agree that localization support should be built into bevy at some point, but its a complex topic and no obvious solution emerged yet. IMO its best to experiment with things in third party crates first and then later upstream something that works well.

Anyway, I don't want to derail the conversation, that's something to think about later on :)

prisma hemlock
#

If selection across text blocks is too hard then if I want a piece of text to be selectable it must be in the same block. This puts extra emphasis on customizing spans imo

#

Because i can’t just break a block into two parts and put a custom thing in the middle (like a link)

#

If I want it to be selectable.

robust solstice
#

Obviously we want a hierarchy of text. But the question is, do we want to require that the text hierarchy be represented entirely with the ecs hierarchy? Or do we want to allow the text hierarchy to be represented using the ecs hierarchy, but also allow entity-internal text hierarchies on ECS leaf nodes.

#

There's real value in making text-hierarchies a component in-and-of-itself, and it shouldn't prevent you from ignoring the internal-hierarchy and just using the ecs.

#

Does that make any sense?

prisma hemlock
#

That ignores the whole “text selection across blocks is hard” problem

robust solstice
#

Over time, I've come to appreciate apis that can stand on their own but are extended and improved by an ECS layer on top. As opposed to a tightly coupled ECS only api.

robust solstice
#

If you want to have every span be a component on an entity, then you need to solve the cross-ecs-hiarchy selection anyway

prisma hemlock
#

I’m just repeating what was said above:

#1248074018612051978 message

prisma hemlock
#

Then the parent aggregates text from its children

robust solstice
#

Right... how is that in any way incompatible with having nodes also contain a hierarchy of text internally in addition to having a hierarchy of child text elements?

prisma hemlock
#

That’s the solution you’re advocating for but that’s not the impression I get from others.

robust solstice
#

Fair enough. I think I must be misunderstanding something. I'll have to come back to this later, maybe I will grok it better.

prisma hemlock
#

The main issue is I’d like to create custom behaviors for text spans AND have them be selectable with other spans.

#

Oh and also being able to query spans would be nice. Like creating a system that does some sort of animation just for specific spans.

#

You could have a custom spans system but that feels like a poor man’s ecs and it’s still not queryable.

tawny oasis
# wooden drum Agreed, markdown seems fairly limited for our use cases. Colored text would be a...

Where possible experimenting outside the repo is definitely the better option, but it's important that we know what features we are working towards so we make something that works for the future, rather than something that's only technologically cool but useless for real games and apps ... Regardless of how we build translation or the format we choose for rich text, the design of bevy_text shouldn't be meaningfully impacted by those (since the other things should be optional layers on top)

tulip hinge
#

This illustrates how things work today and the two current proposals for changing how things work:

#

but consider that we might want more behaviour: we want to mutate the first span, we want the second span to be a link, we want the third span to change colour on hover, here's how one might tackle these requirements in each of these designs:

#

I think in all of these cases we want some more ergonomic API to construct Text... but that same API could generate any of these forms, so what's the easiest to work with after that?

prisma hemlock
#

In the style ranges case, how would you handle multiple links in the same block?

tulip hinge
#

the "inverted" components, so to speak

prisma hemlock
#

Oh I see you have an array

tulip hinge
#

yeah it's clunky

prisma hemlock
#

So is the only gripe with spans as entities the syntax?

tulip hinge
#

in the style ranges case I don't know how you mutate the sub-string... you'd have to provide some helper methods like "insert text here"

#

since you need the range to be updated as well as the string

#

all the ranges*

prisma hemlock
#

And I'm curious, as someone using this api, how you compute the ranges?

tulip hinge
#

again, it'd have to be provided by bevy

prisma hemlock
#

Just making sure I'm clear on how it works

tulip hinge
#

and considering it doesn't map 1-to-1 with the cosmic-text representation, I'm not sure we gain that much

#

in the style ranges case I think you'd have to provide rich-text-tree-manipulation APIs, or treat it as an immutable kind of API, user just provides the representation and you just regenerate it every time

tulip hinge
prisma hemlock
finite magnet
#

I want to give my perspective on this discussion as a third-party crate author. Given a good foundation for rendering text, things like selection (even multi-block selection), markdown, and so on are all problems I can solve in my crate. While it might be nice if Bevy supported these features, I am in no way blocked by the lack of them.

What I am more focused on, for purposes of this working group, are the things that cannot be done without changes to Bevy. This includes how fonts are loaded and how styles are represented at the entity level.

finite magnet
tulip hinge
#

I agree

finite magnet
#

I think the more interesting scenario is the "throbbing hyperlink": where you have a single word in a paragraph of text whose color and opacity is being driven by an animation curve or timer. Admittedly this scenario is completely made up - it's not something I have actually seen - but it's conceivable. I bring it up simply because it's a case which is easier to implement in some style representations than others.

white monolith
#

Why not just try to expose the cosmic-text API as much as possible (1-2-1 )?
1 entity = 1 cosmic-text buffer/editor with array of array spans, something like this: https://github.com/Dimchikkk/bevy_cosmic_edit/blob/main/examples/font_per_widget.rs#L24

And bevy exposes editor/buffer proxies via components... basically to give user access to low level api to build anything cosmic-text is capable of on top of that.

GitHub

Contribute to Dimchikkk/bevy_cosmic_edit development by creating an account on GitHub.

finite magnet
# white monolith Why not just try to expose the cosmic-text API as much as possible (1-2-1 )? 1 ...

One of the things we've talked about is possibly switching from cosmic-text to parley in the future. At the moment we decided to go with comic because it's the more mature solution. However, parley shows great potential (for example, support for variable fonts).

For this reason, we might want to leave the door open to support such a future migration, in which case we don't want to expose too many details of the underlying solution.

shadow marsh
#

TextSection are essentially that thought process, but applied to your previous text stack. It just happened to be intuitive enough that it worked as a mid-ish level API for users.

I feel like we would definitely need something on top of cosmic's API to match the userfriendliness there

white monolith
shadow marsh
#

I feel like spans-as-entities has a lot going for it, but our current tools for working with hierarchies might make it a net dev-ex downgrade at the moment, and it seems like there'd be a huge amount of overhead in creating / modifying text to get back down to cosmic spans.

#

Could be worth trying out though to see how it looks.

white monolith
finite magnet
#

Here's an example of what I mean: from what I understand, both cosmic and parley are similar in how they represent style spans. However (and I don't remember which one does this) one allows overlapping spans, while the other does not - meaning that you have to normalize the spans if you have overlapping styles.

finite magnet
#

The problem, for me, with the existing system is that I can't mix text and non-text elements in a single paragraph. For example, if you replace name in the previous example with the user's profile photo, that's an entity, and you can't put an entity in the middle of a text node. Right now I simply punt on this issue: every string creates a text entity with a single TextSection, I don't even try to support multiple sections with different styles.

#

While it may be a lot of work to to convert a list of entities into a set of text spans that cosmic-text can consume, there are some important use cases that simply cannot be supported without doing that work.

#

What I would propose is a new type of layout option: right now we have Flex and Grid. The new type would be called Block or Flow, and would mean "layout your children using a word-wrapping algorithm, combining text entities into merged spans where possible." So the algorithm to convert text entities to spans would be strictly opt-in; if you put text entities in a flex or grid container they would still be separate entiries and would get their own cells.

white monolith
finite magnet
tulip hinge
#

I've prototyped spans-as-entities so you can see how you feel about it: https://github.com/tigregalis/bevy_spans_ent

It's built as a layer on top of the current bevy_text (we just update the Text component on the parent, based on the changes in its children), but ideally it's not a layer at all.

Ideally there's a better way to construct these, but that can be built as a layer on top of that.

#

What would really prove the idea is span-level hover and click events. That shouldn't be too hard to do, assuming we can access the hit detection methods from cosmic-text's Buffer (if not... we would have to expose it out of bevy_text).

finite magnet
wooden drum
tulip hinge
#

The same way it works now. bevy_text preserves line breaks, cosmic-text drops them.

prisma hemlock
#

Nice!

tulip hinge
#

To further elaborate, it's possible to have a span in bevy_text with nothing but a "\n". Cosmic-text still handles the layout.

Something else that comes to mind... with spans as entities, mixed content (e.g. inline boxes like images, which would also be entities that are children of the parent Text entity) makes more sense.

#

Theoretically it should be possible right now to hack in mixed content (inline boxes) with this method. We'd just need to somehow provide a fake font to cosmic-text that lets us draw transparent glyphs of arbitrary size. Of course, it'd be better if cosmic-text supported it out of the box.

shadow marsh
# tulip hinge I've prototyped spans-as-entities so you can see how you feel about it: https://...

That's great, thanks for putting that together. I will play with this later. One scenario I'd like to see is:

There are many monsters with HitPoints components.
They have health bar entities as children with text like HP: 5 / 10. (Possibly with further indirection from an additional sprite component for the bar itself)
I want to update a the text for a specific monster's HP value when their HitPoints component changes.

My intuition is that this actually wouldn't really be worse than current-bevy. You're having to do hierarchy traversal either way and we do have helpers for that.

But there may be examples where you previously didn't need to do that and now you do.

tulip hinge
#

We're assuming the numbers have their own styles?

shadow marsh
#

Yeah, they are broken into separate spans for that reason.

prisma hemlock
#

With spans as entities you should be able to target a specific span with a marker component

shadow marsh
#

Yeah, that's demonstrated really well in tigregalis' repo above. It's just the extra hierarchy traversal to get the right entity to modify that's a little less friendly, I think, that might be a slightly dev-ex downgrade.

tulip hinge
#

Yep so I'd have (TextSpan, CurrentHealthDisplay(Entity)) and (TextSpan, MaxHealthDisplay(Entity)) , then a query (Monster, CurrentHealth(u32), MaxHealth(u32))

prisma hemlock
#

I don’t think you’d need a traversal for 👆

shadow marsh
#

But I suspect that the situations where this comes up might be a little more rare than I thought and that overall it looks pretty great.

shadow marsh
#

That's interesting I guess.

tulip hinge
#

it's a 1 to 1 relationship so you can get away with it

shadow marsh
#

Spawning that seems a bit annoying

tulip hinge
#

does Bevy not provide a helper for referencing the parent entity in children?

#

ah wait that's not relevant

#

they're not parent-child

#

actually it would be
Monster (sprite) -> Text2d -> TextSpan

theoretically you should have the monster entity at the time you generate the textspan

prisma hemlock
#

And like i mentioned above you could make this a lot more ergonomic by using a very simplified bbcode style syntax and a map that maps named spans to a list of components

#1248074018612051978 message

#

Perhaps in a macro

shadow marsh
crude tinsel
#

It seems like people are thinking that we might end up with "spans as entities" OR something a "bbcode/markdown/html" interface as frontends to text I'm Bevy.

#

Whereas I reckon it probably makes sense to have both.

shadow marsh
#

My thinking is that the "bbcode/markdown/html" angle is less interesting / totally open for experimentation in third party crates, and I'm more interested in what the midlevel APIs look like.

prisma hemlock
crude tinsel
prisma hemlock
#

Well ultimately it all needs to be converted to cosmic text data so yeah

#

But i think a bbcode interface should be on top of spans as entities

#

What I'm advocating for can barely be called bbcode. Literally just the [A][/A] bracket syntax. If you can attach components to spans then there's not much need for a fancy template format

tame aspen
#

Very flexible and intuitive

crude tinsel
#

Are we imagining something like a TextStyle component?

prisma hemlock
prisma hemlock
tame aspen
thorny owl
#

[A][/A] implies nesting of styles, and then you'd have to handle e.g. two styles providing different values for the same component

#

if no nesting is allowed, it can just be [A] with no [/A]

#

(not to imply that i think nesting should or shouldn't be supported)

prisma hemlock
#

Nesting would definitely be useful but I'm not an expert on cosmic text or parley

crude tinsel
#

cosmic-text accepts non-overlapping spans. So any nesting needs to have been "resolved" into a flat structure by the time it makes it there. But that can be done quite easily when translating the tree into flat style spans.

prisma hemlock
#

Although nesting might make this difficult

crude tinsel
#

The tricky thing is how to deal with a user that writes:

[A]Hello[B]Wor[/A]ld[/B]

prisma hemlock
#

Is that valid? I think that should error honestly

crude tinsel
prisma hemlock
#

Well I imagine we'd have some sort of validation

#

Seems implied that there would be some error cases

crude tinsel
#

If you're using a "ranged styles" model then it can be valid. If you're using a tree model then you'd need something like what the HTML5 spec does to disambiguate the parsing.

crude tinsel
#

If you treat these as flat, overlapping style ranges then you can render this as:
Hello_Wor_ld

#

If you try to parse it into a tree then you end up with problem. You have:

- B
  - Hello
  - I
    - Wor

Then how to do you complete the tree?

#

I believe BBCode is typically compiled to HTML. So you'd get a tree, and you'd leave the HTML parser to deal with ambiguity. But something like MS Word would typically use something more like the ranged styles model.

robust solstice
#

tree style seems like the way. I like this api, but I will say it doesn't seem super general. Is there a way to refer to generalized components names in the string without having to manually add components to each of the entities? How will this work with BSN, where the components on each subspan need to be encoded statically?

#

Perhaps that stuff would have to operate on a higher level?

crude tinsel
#

A tree with spans-as-entities definitely seems to me like it would be the way to go for BSN

prisma hemlock
#

Assuming BSN can contain maps that might work.

#

I think adding components to the template string itself is route to a lot more complexity

#

And it potentially adds a lot of noise to the string. Keeping styling and components separate means the template string can be much more terse and readable

shadow marsh
#

In my "bbcode-ish" rich text stuff, I sidestep this and allow only hello [red]world[blue]!. hello is default style, world is red, ! is blue. Really inflexible, but that's all I need.

sweet scarab
#

if it supports inline annotations like that it will also need to consider escaping and such

prisma hemlock
#

Personally I’d prefer to avoid blowing up the scope of the markup. I just see it as a tool to mark which parts of text belong to which spans in a terse and readable way, but that’s just my approach.

robust solstice
#

I don't think we can accept anything unless we can work out how it will be represented in bsn. That's a hard requirement for any sort of markup. But I think it's probably safe to move forward on the spans-as-entities idea with hand-waves about markup for now.

sweet scarab
prisma hemlock
tulip hinge
#

IMO we should aim for a flat structure, i.e.

[b]Hello[/b][bi]Wor[/bi][i]ld[/i]

#

That would be reasonably straightforward to support

#

consider some other scenario, [x]foo[y]bar[/y]baz[/x]:
This would become the tree

x [
  foo
  y [bar]
  baz
]

intuitively I'd expect to be able to address "x" as a unit, but there's not a clear way to support that

I'm not saying we can't eventually support a deeply nested tree of nodes, but it's much less clear how to handle it compared to a flat list

#

And anyway a well-formed or malformed tree structure can be flattened into a flat list, so someone determined enough could build it as a layer on top if they so chose.

tulip hinge
#

Ok, building on top of spans as entities, I've now got:

  1. A convenience macro
  2. An initial implementation of hover for bevy_ui text
  3. An initial implementation of "link" clicks for bevy_ui text

It's not perfect, but it's a starting point

prisma hemlock
#

Sweet!

shadow marsh
# tulip hinge Ok, building on top of spans as entities, I've now got: 1. A convenience macro ...

It's cool to see the buffer hit detection all wired up. Though it makes me wonder about something like "spans as interactable UI nodes."

The monster example turned out pretty clean too -- I hadn't really thought about redundantly storing Entity links within a Bevy hierarchy. I think overall it feels a bit better than using iter_descendants to find the span. Though I tried this and it wasn't nearly as bad as I was imagining, the entire thing is +1 LoC.

Also TIL about ChildBuilder::parent_entity().

tulip hinge
# shadow marsh It's cool to see the buffer hit detection all wired up. Though it makes me wonde...

Yeah I always just assume the library I'm using has the convenience methods I need and search the docs, and most of the time my faith is rewarded. Rust is the best that way.

I see an Entity as just a glorified reference/pointer/index. The ECS handles the safety for us, so we can store those "pointers" wherever we want.

If we had fast and ergonomic entity Relations that might be how I would prefer to model it though. When we do get that, spans-as-entities become even more compelling as the base representation.

Bevy has all the tools needed, so with spans-as-entities as the foundation, writing those extensions was easy; it basically wrote itself.

Without it, I'm not sure (for example) how to model links in a nice or at least obvious/simple way. It's certainly possible if users can attach data to spans, similar to what cosmic-text does (we pass the span index usize to cosmic-text, which stores it as Attrs::metadata) then you could have any sort of Map<usize, T> but then I feel like we're getting into "it's just worse ECS" territory.

shadow marsh
#

I suppose it's relatively safe in this particular example without Relations because the pointer is within an existing Bevy hierarchy / everything is cleaned up by despawn_recursive, etc.

#

It would be slightly painful if spans-and-entities and BSN happened on separate release cycles, but spans-and-entities seems quite useful so maybe that shouldn't be too much of a consideration.

crude tinsel
tulip hinge
prisma hemlock
#

Awesome! This is looking great

shadow marsh
#

I can't wait to delete bevy_simple_text_input and its janky cursor, lol.

tulip hinge
#

might need some help "productionising" this though

#

selections should be fairly straightforward

white monolith
warped jolt
#

Hi, I've encountered an annoying issue with fluent where they insert directionality characters that are rendered as rectangles by Bevy's default font (and by FiraSans). Context: https://github.com/projectfluent/fluent-rs/issues/172 and https://docs.rs/fluent-bundle/0.15.3/fluent_bundle/bundle/struct.FluentBundle.html#method.set_use_isolating and https://unicode.org/reports/tr9/#Explicit_Directional_Isolates .

It looks like these characters should not be stripped at the library/localization framework level, so I'm wondering if the displayed rectangles should be considered a bevy_text bug.

shadow marsh
warped jolt
shadow marsh
#

Yeah

warped jolt
#

Nvm figured it out.
Here's my v0.14

#

And here's my bevy main. Idk where the text went

#

Hey it works! (I needed a CosmicBuffer in my custom text inserter)

tame aspen
forest dove
#

isn't rustybuzz already pretty much that?

tame aspen
#

Yeah, but it's explicitly a catch-up effort, and not Google-backed

forest dove
#

I meant more in the sense that google could use/improve rustybuzz instead of make a competitor

#

but I'm not complaining either, more rust project being funded is always nice to hear

forest dove
tame aspen
#

Yeah, I wish it was more clearly stated

robust solstice
forest dove
robust solstice
#

Yeah RustyBuzz will be able to use Fontations, instead of ttf-parser.

#

So google is just trying to unify the stack, looks like.

crude tinsel
# forest dove or, more accurately, I'm mostly curious why they didn't do that. It's not mentio...

My understanding is that there are a couple of possibilities for a google-sponsored shaper:

  1. A version of Rustybuzz. Probably a fork (because Google).
  2. A "next generation" shaper based on Swash. Which has the potential to unlock performance improvements.
  3. Some kind of hybrid of the two.

None of which are happening right now (google are continuing to use Harfbuzz) because there is no funding approved for such a project, and because the team that would be likely to work on such a project is still focussed on their Freetype replacement (skrifa/read-fonts from the fontations repo).

tulip hinge
finite magnet
#

At some point I want to make a fork of my UI framework that uses the new cosmic stuff. However, given that I've only just released a version that works with Bevy 0.14, I don't want to immediately change the deps back to Bevy mainline, except in an experimental branch.

finite magnet
#

Also, a related question is how are you routing key press events? Simply reading key events from the global event queue won't work if you have more than one input field. The way I handle keyboard events in bevy_quill_obsidian is that there's a system which uses the bevy_a11y Focus resource to decide which entity to dispatch keyboard events to. This consists of several parts:

  • A set of bubble-able events for key chars and key presses - these are similar to the existing Bevy events, but can be bubbled via bevy_eventlistener.
  • A system which reads the global keyboard events, converts them to the bubbled versions, and dispatches them to the entity stored in Focus.
  • If no entity has focus (Focus.0.is_none()), then it targets the entity with a DefaultKeyListener component.
    This means, for example, that you can have global shortcut keys, but if a text field has focus, then it can intercept those events and consume them (using .stop_propagation()) rather than invoking the shortcut.
#

Quill also has a system for supporting tab-navigation between fields, but that is well outside of the scope of this channel.

shadow marsh
tulip hinge
#

There just needs to be a notion of Focus or Active (which can be components or a resource) I think. I would be starting with bevy_ui's own Focus.

I've deliberately been building it outside of bevy_ui first, partly to see if there is anything further that needs to be exposed directly, and so far there isn't. Although there are some more things cosmic-text may potentially expose directly, since I had to copy-paste some snippets from it.

Currently there's no integration with spans as entities as well. But I will separate those efforts anyway, it was just convenient when I started it and evolved naturally.

tulip hinge
finite magnet
#

There's no dependency between that and bevy_ui.

tulip hinge
#

Got it

#

Are there any built-in systems that modify the Focus currently?

finite magnet
white monolith
tame aspen
#

It's a very simple solution, since it relies on functionality from other crates, which means that the tech debt cost is low

twilit iron
#

been quite sick for a while, ugh, but finally up and walking about again.

I agree this can be merged, however, me and I assume anyone else using this would wonder how to use the installed system fonts? I mean there could be quite a few?

And the example still uses the FiraSans-bold.ttf loaded through the assets server, so I assume that loading system fonts only gives us fallback in the case of a missing glyph?

#

So while the tech-debt is extremely low, what we do except increasing startup time by a second, and as it seems overriding the chosen font due to a setting is a bit confusing and would have to be clarified

sleek raft
shadow marsh
#

Seems unfortunate that emoji only render correctly if the text color is pure-white. As a user (this is pretty likely not universal) I would rather have support for font fallback where I manually provide fonts via the asset server. And none of my projects have a use for it at the moment, so off-by-default makes sense to me. (this is the current behavior of that PR as far as I can tell).

white monolith
white monolith
twilit iron
#

I rather have a way to define the font-fallback from within bevy as well.

But this PR enables the cosmic-text way of falling back to missing glyphs using the system default, and thus the documentation,and namings should reflect this.

#

So maybe having TextPipeline::with_system_font_fallback() and TextPipeline::default() would make more sense, currently, until the rest of the features are implemented.

The member variable actually is correctly named, its mostly that this also enables the fallback thingies because cosmic text by default tries to find a fallback glyph.

an interesting test, would be to find the font used from the systemfonts, load this manually into bevy, use both fonts in text, and then try to use the font without the smiley, and I would actually expect that cosmic text would manage to fallback even without system fonts

#

my 2 cents 🙂

white monolith
#

Right, so this PR doesn't enable the font fallback feature, it's the default behavior. This PR allows to load system fonts that can be used as font fallback. I suggested rename the member variable to indicate that system fonts are loaded primarily for font fallback... because we don't expose yet any other features like querying loaded fonts from font db for it to be useful.

tulip hinge
sonic island
#

Sorry if this has been answered but which branch are y’all working on? If I want to test the work in progress which one would it be?

tulip hinge
# sonic island Sorry if this has been answered but which branch are y’all working on? If I want...

cosmic-text integration has been merged into main.

minimal system fonts (font fallback) by @white monolith is on a PR to main.

i've made a proof of concept of spans as entities as a plugin in a separate github repo. if people like the concept in general, i can make a PR on bevy main to properly implement it, but I think there's some reticence without a story around ergonomic construction of these text nodes (e.g. macros or a dsl, though I've made a first attempt at that too). personally i really like it. i'm not really working on it anymore, since the general idea is done and it just needs some consensus on the way forward, and anyway i think a text editor widget is more impactful in the near term.

the editor widget i've just got as an example in spans as entities (though no relation really). i'll pull it into its own repo at some point (probably the weekend) so it can be consumed as a plugin, and iterate on it from there. once it's got enough features and extension points i plan to then upstream to bevy in a PR. it seems orthogonal to everything else that's being discussed here, and so far i'm implementing it entirely outside of bevy without any need for changes to bevy

twilit iron
#

Yeah looking at the code for bevy_spans_ent I can see where it shines, and also where it really doesnt shine.

Modification and targeted changes to parts of text mainly is where i see this shining, since just putting a marker component onto the part makes it easily targeted by both specific to that part, or entire categories of text-parts.

Where it doesn't shine is where every part of every text is now a global variable, and would, if implemented in bevy mean that as a user that are always interested in the whole text-section, with different formatting for different parts, I would have to assemble that from the floating around parts, and the parent. Compared to just getting the TextEntity.

twilit iron
#

So basically, the path forward would have to be decided around the most common usage pattern.

Will people want the animation of text, more than they want the simplicity of the entire text section accessible.

IMHO I think having the spans as entities as a separate or addition API on top of the one we already have makes more sense, and for me that is much due to the fact that in my usecases, the entire textfield is almost always what I want to accesss. Clickable links is an exception to this, but its also kinda similar to writing something like "underscore text underscore" and have that automatically be formatted as "italic"

And these kind of things are kind of text-aware styling parts, that I do find important, but also best experimented inside third-party crates for now.

#

Maybe spans as entities would be much easier and the APIs for text much clearer once we have better entity-relations, and better spawning of hierarchies.

So for me: having text-editing input such as tigregalis is implementing inside bevy_spans_ent (doesnt seem to be too much work to build ontop the current textsections way from what I can see at a glance) would be the highest priority.

And actually having the bevy_spans_ent as a third-party plugin, would allow us to gather some feedback from the community on which pattern is nicer to work with.

white monolith
prisma hemlock
white monolith
prisma hemlock
#

But as long as spans as entities exists and is well supported then I don’t really care if it’s built on a lower level api.

tulip hinge
#

I had a couple spare hours and ripped https://github.com/tigregalis/bevy_text_editor out of https://github.com/tigregalis/bevy_spans_ent and pluginified it, so now the example is (where setup, animate_cursor and animate_selection are userland systems):

use bevy::prelude::*;
use bevy_text_editor::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(TextEditorPlugin)
        .add_systems(Startup, setup)
        .add_systems(Update, (animate_cursor, animate_selection))
        .run();
}

fn setup(mut commands: Commands) {
    commands.spawn(Camera2dBundle::default());

    let style = TextStyle {
        font_size: 70.0,
        ..Default::default()
    };

    commands.spawn(TextEditorBundle::from_sections([
        TextSection::new(
            "Hello, ",
            TextStyle {
                font_size: 40.0,
                ..Default::default()
            },
        ),
        TextSection::new(
            "World!\n",
            TextStyle {
                font_size: 60.0,
                color: Color::srgb(1.0, 0.0, 0.0),
                ..Default::default()
            },
        ),
        TextSection::new(
            "Hello, Bevy!\n",
            TextStyle {
                font_size: 50.0,
                ..Default::default()
            },
        ),
        TextSection::new("and so on and so forth...", style),
    ]));
}
shadow marsh
tulip hinge
#

Editor<'buffer> either:

  1. borrows a buffer (via &mut) which we can't pass around (i.e. into the ECS / across frames), or
  2. it owns a buffer which means there are two copies of the buffer, or
  3. it holds an Arc<Buffer> which means the other buffer also needs to be Arc'd

I didn't like any of those options.

#

Actually #3 ends up cloning the buffer when you call Arc::make_mut, so it's effectively the same as #2 but worse. Arc::get_mut wouldn't be possible since there would always be one other Arc.

#

So the canonical approach would probably be to impl Edit for MyType but it would basically involve copying/reimplementing Editor<'buffer> almost entirely, and a lot of that code is logic-dense and non-trivial. But I have thought about looking at that approach at a later stage.

Another approach is to simply not make use of cosmic-text's Edit or Editor and build it from scratch for bevy but until there's a compelling need for that I'd rather just use the tools available.

#

Oh and there was also the option of replacing the Buffer with Editor<'static> in bevy_text, for everyone (then everything that reads/mutates the buffer accesses it via the editor), but partly I wanted to avoid making changes to bevy if possible and partly I just wasn't a fan of that approach.

shadow marsh
#

Cool, thanks for the explanation.

replacing the Buffer with Editor<'static> in bevy_text

That's the sort of solution I was initially imagining, but none of the options seem great.

finite magnet
#

One thing to bear in mind in terms of editor performance and complexity: For every "full-page" text editor widget, there's probably 100x as many widgets that merely edit a single line of text - 256 characters or less. (If you consider all the form field inputs on the web, the true ratio is probably more in the range of 10,000x.) A full page editor typically has a complex data structure for a buffer, whereas most text input fields just edit a simple string. One should expect that someone who is taking on the challenge of building a source code editor will have to put a lot of thought into their data structures, whereas for the vast majority of users who simply wants to enter the name of a player or saved game label, things should "just work" in the simplest way possible. What I would not expect is a single universal editor widget that somehow manages to optimize for the full range of use cases.

visual hornet
#

Typically modern UI renderers will omit data that doesn't need to be rendered. Things like virtualized tables, lists, etc do exist and egui has them currently. The actually amount of on screen data is usually more dependent on screen size rather than number of widgets.

wooden drum
prisma hemlock
#

When I suggested bbcode the only purpose was to assign labels to spans of text. IMO users should supply a TextStyle component instead of shorthand like [c=#ff00ff]. I really don't think bevy should go down the road of supporting an advanced text notation.

Instead of a hard-coded settings struct you could supply a mapping from label -> list of components. When that span is spawned you'd just look up the label in your map and insert the given components onto the span.

#

A big benefit of this approach is you don't have to re-parse bbcode when you want to change a piece of text (like in the dynamic example). You just parse it once, then do an ECS query for the piece of text you want and change it that way.

#

For example, this template: FPS: [fps]0.0[/fps] and this span-component map "fps" -> [FpsValue]

This could create a text-span with the FpsValue component. Then whenever you want to update, you query for FpsValue and change the text for the given span.

crude tinsel
prisma hemlock
#

Taken to the extreme you basically have another version of BSN just for text.

mystic nest
wooden drum
#

Ah I see, that's interesting.
I suppose there is different trade offs one can make, your approach is probably more flexible (although nesting might be more difficult) and performing, while the other one is a bit easier / less boilerplaty to use.

But I agree with Nico, this is quite easy to build on top of bevy text so we can just keep multiple solutions in third party crates (maybe sharing the parser)

wooden drum
crude tinsel
crude tinsel
wooden drum
shadow marsh
mystic nest
robust solstice
#

there is discussion happening in github as well as #1264881140007702558

prisma hemlock
#

Sorry commented in wrong place. Deleted and moved

wooden drum
twilit iron
# shadow marsh <https://github.com/bevyengine/bevy/discussions/14437> seems to be endorsing spa...

Yep, it seems it does, mostly because it lends itself much better to the BSN things.

So I guess the spans as entities question/discussion will probably hinge much on the how the BSN things fall out.

If the BSN macro/fileformat is the way to go (seems very likely), I guess spans as entities matches the bevy scenes way better and makes more sense.

The impl @tulip hinge has could probably be ported over quite quickly then.

#

And with quickly, I mean when the BSN path forward is certain, (maybe it is idk) we might as well move the text-apis in that direction as well.

tulip hinge
#

Cart's comments on the BSN proposal heavily imply that a text representation that's not entities-based is too challenging to work with. But as @prisma hemlock has said previously, it could just be an implementation detail i.e. there could be an underlying hidden implementation which looks closer to what it is today (or closer to Buffer) but the public API even at an engine-dev level is spans as entities. Working on the editor widget, where I have to "write back" into the Text component, I've run into a couple bugs where maybe a "managed" representation that's closer to Buffer could be easier to integrate. But the obvious thing there is to make Buffer itself the representation, and so the current Text component largely disappears (except for config that applies to the whole "Text Area").

tl;dr: currently Text { sections: Vec<TextSection> } -> cosmic_text::Buffer, proposed (Entity, TextSpan,) -> cosmic_text::Buffer

#

Hmm, on second thought it wouldn't make writing back any easier, but it does make an intermediate Text component (between spans and buffer) redundant

#

one more thing that's unlocked is turning TextStyle into 3 or more components: TextColor, FontSize, LineHeight (new), and 1 or more optional components to represent Font Face selection like Handle<FontFace> vs FontFamily(String), Emphasis, Stretch, Weight: I think if there's a Handle<FontFace> we prefer that selection, otherwise we query for a font based on these components.

crude tinsel
#

there could be an underlying hidden implementation which looks closer to what it is today (or closer to Buffer) but the public API even at an engine-dev level is spans as entities.

This seems to be the common pattern for codebases that represent text as entities. Chrome does this. Servo is switching to do this.

#

I don't think anyway really wants TextSections? They just haven't been removed yet post cosmic-text

finite magnet
#

I also want to lobby support for inheritable text styles, which can be opt-in. Most games use one font. It's much less onerous if the user can specify that font one time, at the root of the entity tree, than having to pass an asset handle to every bit of text.

tame aspen
#

Agreed, although the new Construct trait Cart's cooking up will make the passing around handles bit nicer

#

But still, being able to set default fonts is great

#

(and I think that most games probably use two fonts)

crude tinsel
tame aspen
#

Here's a good stress test for your designs: "when a button is hovered, bold its text"

#

The existing abstraction fails completely here: there's no way to preserve the font family

#

And there's also no general way to look up "the bold variant of a given font"

finite magnet
#

BTW, on the topic of rich text: I think the right approach is not to focus on BBCode or HTML, but to ask the question: "What rich text encoding format does my localization library support?"

#

Localization libraries do support limited forms of rich text.

#

However, because translators are often sub-sub-contractors (or in the case of open source, volunteers) they are outside the inner circle of trust of developers. For this reason, localization pipelines are set up in a way such that translators cannot see the actual style markup, but instead see placeholders which are then replaced when the text is loaded. This prevents translators from damaging the markup, or inserting malicious code (such as a <script> tag in a web app).

#

I don't know which localization library is preferred for Rust / Bevy projects, but whichever one is likely to be used I would start by investigating what kinds of markup it supports for localized text strings, and then build a text-span converter based on that.

tame aspen
#

There is a clearer winner, we should align with it

finite magnet
# tame aspen Fluent

I'm not familiar with Fluent. I've worked extensively with i18next and other JavaScript-based solutions. Generally, the frameworks that I have worked with are more than just a library for consuming localized content, they are also part of a CI translation pipeline which extracts the source text resources from the project repo, maintains a database and dashboard showing which strings have been translated into which languages, and interfaces with various translation-as-a-service APIs 🙂

tame aspen
#

Yep, I think that we may ultimately need to build out the latter half of the pipeline 🙂

finite magnet
#

When I worked at AWS, they had an internal service that used machine translation for a first pass, then a second pass that used human review. This service was compatible with a bunch of different formats: JSON, XML, HTML etc.

#

In any case, I just briefly glanced at the docs for fluent - it seems like it's the lowest-level piece of what I have described - a runtime for loading strings - and I'm not sure what kind of markup, if any, it supports. It seems rather unopinionated, which may or may not be a good thing in this case.

wooden drum
#

I think Fluent does not have any rich text built-in, so we could use anything we want (at the cost of translators having to understand how to use the markup language)

tame aspen
#

For Text entities, it replaces the TextSection if it changed. (It doesn't handle multiple sections, for the same reasons that cart pointed out in the BSN doc).
@finite magnet so I take it you feel strongly that spans-as-entities is the way forward?

tame aspen
#

Which makes sense to me

#

Fairly unfortunate in the short time scale, due to how much pain more hierarchies are

#

But I think that's something we can work out

finite magnet
#

Let me elaborate on that a bit.

tame aspen
#

And it makes the component design really nice

finite magnet
#

The reason it's complicated is because you've done all this work to resolve diffs/patches to entity hierarchies - and now you have to do it all again to work on a different hierarchy made up of different things called "text sections".

tame aspen
#

Which is also something I've felt as a user

#

And for most of my text I just want a single section

fossil void
#

the one thing I don't really understand about spans-as-entities is how you do ordering of the children?

shadow marsh
#

Children is just a vec. It's ordered / sortable.

finite magnet
#

I feel like maybe we have gotten off track, we've talked about a lot of things but there's been little tangible progress since the cosmic-text PR landed. I think the most important next step - and one I think most people can agree on - is the whole "font-family" abstraction, that is, being able to say "I want a bold version of the current font" without having to explicitly name the bold font asset.

My first question would be, is anyone actively working on this, or plans to in the near future?

#

One problem that I think Bevy has is that the ratio of core devs to volunteers is very small, which leads to a classic "diffusion of responsibility" problem - everyone sees what the problems are, but thinks that someone else is going to fix it. The general solution for the DoR problem is to assign tasks to people, but you can't assign tasks to volunteers, they can only...volunteer.

tame aspen
#

Seconding all of that 🙂

#

That font family abstraction is the next thing I'd like

#

And I'd love a concrete proposal

#

I think this is relatively straightforward, so I trust y'all to come up with something nice

tulip hinge
#

What sort of API would you like to see? This is how font faces are selected in cosmic-text, so we just need to be able to construct one of these:

pub struct Attrs<'a> {
    pub color_opt: Option<Color>, // already exposed in bevy_text::TextStyle
    pub family: Family<'a>, // the family, needs to be exposed in bevy_text::TextStyle instead of Handle<Font>
    pub stretch: Stretch, // variant within a family, needs to be exposed in bevy_text::TextStyle instead of Handle<Font>
    pub style: Style, // variant within a family, needs to be exposed in bevy_text::TextStyle instead of Handle<Font>
    pub weight: Weight, // variant within a family, needs to be exposed in bevy_text::TextStyle instead of Handle<Font>
    pub metadata: usize, // not exposed to bevy_text users, we use it to store the span index
    pub cache_key_flags: CacheKeyFlags, // internal to cosmic-text 
    pub metrics_opt: Option<CacheMetrics>, // internal to cosmic-text 
}

pub enum Family<'a> {
    Name(&'a str), // e.g. "FiraSans"
    Serif, // represents a predefined (common) list of families 
    SansSerif, // represents a predefined (common) list of families 
    Cursive, // represents a predefined (common) list of families 
    Fantasy, // represents a predefined (common) list of families 
    Monospace, // represents a predefined (common) list of families 
}
#

but we should continue to allow a font to be selected via Handle<Font> if the user desires.

my original implementation did something like:

struct TextStyle {
    font: FontFace,
    ...
}

enum FontFace {
    Asset(Handle<Font>),
    FontQuery(FontQuery),
}

struct FontQuery {
    family: Family,
    stretch: Stretch,
    style: Style,
    weight: Weight,
}

impl From...
wooden drum
# finite magnet I feel like maybe we have gotten off track, we've talked about a lot of things b...

I am working on it for my BBCode crate, but probably not in a format that can be adopted by bevy as-is.
I have built a FontRegistry resource which tracks all Fonts which are loaded and then allows you to query them via family, weight etc using fontdb.
This allows the user to just specify a folder path where all fonts are stored and the BBCode transformation can choose the best matching font for the format

#

The load tracking can probably be adopted because its very convenient to just load the entire font folder.
But the font selection should probably be built in like tigregalis suggests

crude tinsel
#

but we should continue to allow a font to be selected via Handle<Font> if the user desires

I honestly don't think we should. I think we should allow users to register a Handle<Font> with the global font database, get a FontId back (newtype around usize or similar) and use that if they wish.

crude tinsel
tulip hinge
#

How do these fit together?

Let us say that we add another step. Something like this perhaps

let handle = asset_server.load("fonts/FiraSans-Bold.ttf");
let font_id = text_pipeline.register(handle);

Now, how should users use FontId to select a font face for their text spans?

crude tinsel
#

I guess there are technically two cases here:

  • Specifying a font-family (e.g. Helvetica) by ID
  • Specifying a specific font within a family (e.g. Helvetica Bold Condensed) by ID
tulip hinge
#

It is font faces rather than font families that have ID's though.

#

I think

crude tinsel
#

The ID's are not inherent to the fonts or families. They're just the key in the vec/hashmap that the font library stores the font/families in.

tulip hinge
#

If you load a collection of fonts, fontdb gives you a collection of font id's, rather than one id

crude tinsel
#

If fontdb doesn't support this atm we could add it. fontdb is super-simple. The whole lib is <1000 LoC

tulip hinge
#

(not that we currently support loading font collections)

crude tinsel
#

If it's giving you a specific font, then I guess we ought not to support bold, italic, modifiers being specified at the same time?

#

Or we just ignore them?

tulip hinge
#

right

crude tinsel
#

Some other "styles" would still make sense (e.g. ones controling wrapping behaviour)

tulip hinge
#

I think wrapping behaviour would stay at the "parent" level

#

currently TextStyle (Span Style) just specifies color, font face and font size (and could further support line height: size and line height get mapped to Metrics in cosmic-text)

#

we just need to redesign the font face selection

twilit iron
#

Some ideas with questions for font selection. Notice I used a singleton pattern, but I think this could be applied onto the Assets<Font> resource as well through trait implementation, or by creating a SystemParam for the fonts and maybe even abstracting away having to use Handles directly.

Not a big fan of having fonts as an enum, where you either specify the handle, or specify a font specification.

// this is how fonts are registred for cusom fonts, or maybe the fontfile itself can provide these parameters instead of having to manually input them?
// this could also be done for all systemfonts, at startup as an option. 
Fonts::register("fonts/Noto_Sans.ttf", FontSpecification {
   family: "Noto Sans",
   bold: false, 
   italic: false,
});

// bold italic font
let font: Option<Handle<Font>> = Fonts::family("Noto Sans").bold(true).italic(true).try();
// the fallback font basically, since this would also allow unwrap_or_default() to work. 
let font: Handle<Font> = Fonts::default();
// probalby the most common usecase
let font = Fonts::family("Noto Sans").try();


// toggling the font boldness
let mut font_modified: FontSpecification = Fonts::specification_from_handle(font: Handle<Font>);
let font: Handle<Font> = Fonts::from_specification(font_modified).unwrap_or_default();

// even nicer would ofcourse be some way to do this. This is where maybe using the handles is a bit to low level 
// and we should specify fonts some other way, and let the font selection (FontSpecification in this example) resolve itself to an handle somehow
// maybe through component hooks or something. 
let font = font.set_bold(true);
#

This is not to far from what we are doing with the asset server today I would say, except that this would require more of a singleton pattern, which might not be wanted. But I am quite certain this could be implemented on as traits onto the Asset<Font>.

This pattern of font selection is based upon me opening libreoffice, and making a font look like I want to, through selecting the font, then toggling the italic/boldness until it looks like I want to.

#

From my perspective, I think to be able to select fonts nicely, we need to have it like this:

  1. load/enumerate fonts in a db (FontDB)
  2. select fonts from this db through an API
  3. apply selected font to the entity in question

maybe 1 and 2 are the only things the user have to do, and 3 is done behind the scenes

compared to how we do it today:

  1. load a font from file directly to handle
  2. apply loaded font to entity.
#

would this be a way?

warped jolt
tame aspen
#

So @true zephyr, currently, text is handled via the Text component, which is composed of multiple TextSection parts in a vec

#

Both @finite magnet and @mental tree have independently found that this is very frustrating to work with in more complex workflows: you end up with two separate hierarchies that need to be managed

true zephyr
#

yeah that makes sense

tame aspen
#

Instead, each TextSection should be its own entity, with a consistent TextStyle

#

In 90% of cases, a chunk of text will have a single text style: we should optimize for making that case easy and ergonomic

true zephyr
#

alright, do we have an API in mind? or is that still up for debate

tame aspen
#

That's the main bit of work: we have broad consensus on "vaguely what it should look like", but not fully mocked API

sonic thicket
tame aspen
#

As I see it, there are basically 3 patterns for text:

  1. A simple label with a single text style
  2. Rich text for things like dialog, with interspersed bolds and italics
  3. Complex forms and document editors
true zephyr
#

Yeah, and for this PR the focus is the first case, with the second one sprinkled in there too, as I understand it?

tame aspen
#

Yep 🙂

#

Ideally this change can be as scoped as possible

#

It's likely to be quite breaking already

#

So I would probably just reused the TextStyle type here for example, despite the fact that it's very likely that we want to break apart the different properties into components immediately after

true zephyr
#

I think I will just whip up something to get started, and open a draft PR. that way everyone can take a look, and once most people agree with the API, we will go for actual code reviews. does that sound good?

true zephyr
#

I think for now I'll keep it simple, simply make a Text component only hold one TextSection, and make necesarry adjustments to examples, tests and docs. We will go from there

true zephyr
shadow marsh
#

It is probably important to keep section_index?

wooden drum
true zephyr
shadow marsh
#

we still need to be able to map glyph layout data to the correct section somehow

wooden drum
shadow marsh
#

that might be the play, yeah. cc @tulip hinge

#

also sorry, may not get a chance to look closer until after work.

shadow marsh
#

tigregalis' editor prototype makes heavy use of the section_index stored as metadata in the cosmic buffer, but as far as I can tell putting an Entity would be fine / more convenient.

i'm not sure if that's possible though -- metadata is a usize.

shadow marsh
#

@true zephyr could you add Fixes #7714 to the PR description?

true zephyr
#

yep, will do

shadow marsh
#

Cool, there's a bunch of relevant old discussion in there that should be linked

shadow marsh
# true zephyr yep, will do

@true zephyr With the current setup in that PR, we end up with a bunch of individual "Texts" being laid out by Taffy, but we need all of the spans associated with a particular Text to be added to the same cosmic-text Buffer so that cosmic-text handles the layout.

There probably needs to be a hierarchy of

Text
  Span
  Span
  Span

Where queue_text iterates the Text's children and adds spans to the buffer.

#

(and yeah, I'd just pick a few examples to update for now)

#

possibly some sort of helper SystemParam for that sort of pattern

tame aspen
#

And then only one query with &mut Text

finite magnet
#

Q: Do we anticipate any performance issues with word-wrapping multiple text entities? I'm trying to decide if the existing display types such as 'Display::Block' should automatically do this, or if we need a new display type ("Display::TextFlow") to opt in to this behavior. Flex and Grid would probably treat text entities like any other entity: each entity is a separate flex/grid cell. If you want a flex cell with multiple text entities, you'd probably need to wrap them in a parent entity.

tulip hinge
# true zephyr I think for now I'll keep it simple, simply make a `Text` component only hold on...

I think we can (and should) make the change to multiple text sections as entities in one "hop", rather than incrementally, since it would involve less code changes:

TextPipeline::queue_text() and TextPipeline::create_text_measure() both take a parameter, sections: &[TextSection].

I suggest redefining Text to:

#[derive(Component)] // the parent component
pub struct Text {
    // pub sections: Vec<TextSection>, // <- removed
    pub justify: JustifyText,
    pub linebreak_behavior: BreakLineOn,
}

and adding #[derive(Component)] to pub struct TextSection {}.

Wherever we are currently calling TextPipeline::queue_text() and TextPipeline::create_text_measure(), first we simply collect an additional query Query<(&TextSection, &Parent)> into a let sections: Vec<TextSection> = ..., then pass sections.as_slice() into TextPipeline::queue_text() and TextPipeline::create_text_measure() .

That should be the extent of the "implementation" side of the change (sans some compilation errors).

Then the "user-facing API" side of the change can be handled from there.

tulip hinge
#

Btw, considering how breaking this is, this is the opportunity to rename from section to a span, which I think we all agree on?

finite magnet
shadow marsh
finite magnet
tulip hinge
shadow marsh
#

Definitely think section index is adequate for now.

crude tinsel
# finite magnet Q: Do we anticipate any performance issues with word-wrapping multiple text enti...

The way I am anticipating this working is a TextFlow (name TBD) component that marks the root of a text flow, with separate TextSpan and Text components on child entities that "belong" to the TextFlow. TextSpans would come with an associated set of styles and Text components would have the actual text String or similar. You could also have Nodes as children of a TextFlow/TextSpan which would behave like inline-block/inline-grid/inline-flex boxes on the web (once this feature is implemented in cosmic text).

These would then get "collected" up into a cosmic-text Buffer (as a single string + style spans that index into it) that lives in the TextFlow component for layout, line-breaking, etc.

#

Web effectively does this too. "Flow" layout consists of Block and Inline layout. Which are implemented largely separately. Block layout is the simple vertical layout of paragraphs (+margin collapsing). Inline layout is the text-based layout (+ inline boxes which flow with the text). The difference on web is that this separation is somewhat hidden from the end-user API: the browser will wrap consecutive sets of inline / text nodes in an "anonymous" block node to separate things out before performing layout.

#

If we wanted to fully support rendering HTML then we'd need this fixup step too. But if we just want to support a "clean subset" of web layout then we could just enforce that text can only appear within a TextFlow.

#

^ Which btw is basically what things like React Native do

tulip hinge
#

What is the advantage of separating the String and the spans?

crude tinsel
#

I think it's also easier to do shaping/bidi analysis on a single continuous string.

#

But given that we're using cosmic-text there is a simpler answer: it's the format it uses.

crude tinsel
#

You can also mix in Nodes once inline boxes are supported

tulip hinge
#

cosmic-text keeps a separate string allocation for each "paragraph"

#

(which is also lossy, it turns out)

crude tinsel
tulip hinge
#

I get what you're saying but having Text alongside TextSpan feels like an implementation detail that's being exposed externally

crude tinsel
#

I wouldn't expect anyone to be spawning these manually

tulip hinge
#

I generally have the opposite expectation. In order to enable the more useful behaviours emerging from spans as entities, people will be adding arbitrary components to text spans.

#

I think being able to nest spans within other spans could come in handy, but it could also be provided by a higher level API

#

or we find a different way of representing children as such

#

on the web, a text node (unstyled) is the most atomic form of text I suppose

#

essentially:

<p>
  <span>
    #text
#

we could represent it that way I suppose

#

which is to imply that Text is a child of TextSpan

finite magnet
#

The templating language can convert from a hierarchical representation to a flat one if that's the desirable ergonomic.

tulip hinge
#
TextFlow <p>
    - TextSpan <b>
          - Text "the"
        - TextSpan <i>
            - Text "quick"
        - Text "brown"
    - Text "fox"
crude tinsel
tulip hinge
#

Ah right so it is

#

my mistake

#

but where is the string allocation?

#

i think each Text would have a string allocation

crude tinsel
tulip hinge
#

I may have misunderstood and we might be talking across one another

finite magnet
#

I want to inject into this discussion something that Alice and I were discussing earlier. Specifically that Quill UI currently only supports a single text section in the text elements, and why that is. Quill has a lot of code for reconciling ECS hierarchies: that is, given a template and an existing entity tree, there's code that determines the minimal set of mutations for bringing that entity tree into conformance with the current state of the template. This includes both the entity parent/child hierarchy and the components.

Now, given that the templating system does all this work to reconcile the entity tree, with text sections as they exist today we have to do all that work again to reconcile another kind of hierarchy with another kind of node - text section. It's even worse, however, because unlike entities, text sections don't have any kind of id, nor is there any easy way to associate an id with a text span. So the reconciler has to work solely on values. Given that the vast majority of widget labels are a single string with a single style, I decided to punt on the issue until we sorted out all the issues that are being discussed right now.

And I get the impression that cart has run into similar issues in the design of BSN. So my ask, then, is that when designing the representation for text that we consider these kinds of use cases - that is, the use case where a sequence of text strings are being synchronized with some (potentially dynamic) source of truth.

#

Note that converting a hierarchical template into a flat representation is a fairly simple problem, and in fact Quill templates already do this for entities.

true zephyr
tulip hinge
#
commands
    .spawn((TextFlow, P))
    .with_children(p|
        p.spawn((TextSpan, B))
          .with_children(|b|
              b.spawn(Text("the".into()));
              b.spawn((TextSpan, I))
                .with_children(|i|
                    i.spawn(Text("quick".into()));
                );
              b.spawn(Text("brown".into()));
          );
        p.spawn(Text("fox".into()));
    );
true zephyr
#

is that desired? Seems annoying for the common case where there is just one section for one Text

finite magnet
#

BTW, in quill templates the children of a node can be any value that implements the View trait - and both String and &str implement that trait. So you can write things like:

Element::new().children(("Hello, ", child_element, "World"));

The strings get converted automatically into text nodes.

tulip hinge
#

Right, it is, and that's the tradeoff, but that's sort of why we've put it off until BSN came along

#

But you bring up an interesting possibility there... we could special case it - would we want to?

#

as an example:

#
// standard
commands.spawn(Text).with_children(|text| text.spawn(TextSection::from("foo")));
// special case
commands.spawn((Text, TextSection::from("foo")));
tulip hinge
true zephyr
#

I think that is not bad, just a check to see if the parent Text also has a TextSection component and add it to the list if it does

tulip hinge
#

only question I have is how should we handle this case

commands.spawn((Text, TextSection::from("foo"))).with_children(|text| text.spawn(TextSection::from("bar")));
true zephyr
#

I guess we just make the TextSection of the parent the first element of the slice we pass to TextPipeline::queue_text(). seems like the most logical choice to me

tulip hinge
#

Yeah that seems logical

true zephyr
#

Should I go make this change, or wait for some more feedback?

tulip hinge
#

I think you should go for it and we can see how it feels. The only thing that may be controversial here is the special case, but that seems like a good ergonomics win.

true zephyr
#

So just to recap:

  • we make Text no longer hold any sections.
  • we make TextSection a component.
  • The process for queueing the text is:
    -- check if the Text parent also has a TextSection, if so, make it the first element of the list
    -- add all child TextSections to the list
    -- submit it
#

right?

tulip hinge
#

Yep that's my thought

#

I had a quick try at doing this (not including the special case) and I'm not quite sure how to make the TextSection live long enough... I think maybe queue_text needs to take an iterator instead

#

Hmm, the meaning of section_index starts to get a bit confused too, with the special case, now that I think of it.

true zephyr
#

I will see how far I come, if I run into issues, I will let you know

true zephyr
tulip hinge
#

this is what I did:

    text2d_query: Extract<
        Query<
            (
                Entity,
                &ViewVisibility,
                &TextLayoutInfo,
                &Anchor,
                &GlobalTransform,
                &Children,
            ),
            With<Text>,
        >,
    >,
    text2d_span_query: Extract<Query<&TextSection, With<Parent>>>,
            let section = text2d_span_query.get(children[*section_index]).unwrap();
            if *section_index != current_section {
                color = LinearRgba::from(section.style.color);
tulip hinge
true zephyr
tulip hinge
#

Yeah I think that could work.

#
let section = if *section_index == 0 {
    maybe_section.unwrap()
} else {
    text2d_span_query.get(children[*section_index - 1]).unwrap()
};
true zephyr
#

we do need TextPipeline::queue_text() to know if the special case is present, is a simple bool parameter fine? I don't see a cleaner way

tulip hinge
#

Yeah queue_text is an implementation detail so I don't see why not

true zephyr
#

also, should we make TextBundle have a TextSection?

tulip hinge
true zephyr
#

even in the calc_name function? do I just grab the TextSection on the parent (and its children), join them together, and done?

tulip hinge
#

I think that would map to the same behaviour yeah

true zephyr
tulip hinge
#

hmm personally I don't think so

#

are there optional bundle components?

#

I think I would be more inclined to have separate bundles, or just document it

#

otherwise we'll basically always have an empty span in the parent

true zephyr
#

true, I guess we should just document it.

true zephyr
#

Never mind, we should create a new TextSectionBundle (name up for debate) because it needs the proper UI components, like Style. otherwise bevy will complain like this:

2024-08-02T09:44:02.485636Z  WARN bevy_ui::layout::ui_surface: Unstyled child `5v1` in a UI entity hierarchy. You are using an entity without UI components as a child of an entity with UI components, results may be unexpected.
true zephyr
#

Not sure what should be in the bundle though...

#

I honestly think this warning should not happen for child text sections. Because what is a Style on a child of the parent Text going to do?

#

I commited what I have so far, I'll go and take a break, and wait for feedback.

tulip hinge
shadow marsh
#

Appreciate the explanation of Span -- I have been thinking more in terms of cosmic spans and exposing that to normal users felt a bit wild.

I do think that

Text (optional default style)
  - Section
  - Section

Seems like a good place to start though. One

commands
    .spawn((TextBundle::default(), LARGE_TEXT))
    .with_children(|parent|
        parent.spawn(TextSection::from("Hello, "));
        parent.spawn((
            TextSection::from(format!("{name}")),
            GREEN_TEXT,
            NameText
        ));
    );

commands
    .spawn((TextBundle::default(), LARGE_TEXT))
    .with_children(|parent|
        parent.spawn(TextSection::from("Hello World"))
    );

Really doesn't seem bad to me. Ergonomics in spawning are offset by ergonomics in modifying specific sections.

Maybe with a hypothetical with_child, the case for a single section could be simplified.

commands
    .spawn((TextBundle::default(), LARGE_TEXT))
    .with_child(TextSection::from("Hello"));

// without the "default text bundle style"
commands
    .spawn(TextBundle::default())
    .with_child((TextSection::from("Hello"), LARGE_TEXT));

Or else with a macro like https://github.com/tigregalis/bevy_spans_ent/blob/main/examples/macro.rs#L20, but I'd rather just wait for bsn! personally.

tame aspen
#

Maybe with a hypothetical with_child, the case for a single section could be simplified.
Yeah, I'd love to see this

warped jolt
#

How does this multi-entity approach mesh with localization, in the case that a paragraph needs to be rearranged? Would the localization text be expected to have a markup that gets parsed at runtime into the entity structure? The problem is basically the same for current text sections honestly.

#

So you’d write the dynamic localization template to the root entity, and then behind the scenes an aggregate localized string is produced, which is then split using a markup into sections.

tame aspen
#

This feels like a case where the templates that @finite magnet loves would be great

warped jolt
#

Related question: how would you implement a (potentially localized) paragraph where certain keywords have an on-hover or on-click effect?

tame aspen
#

Each of the keywords would need to be its own span, and I would give each of them a marker component for this behavior that's update by a system that checks Interaction or its replacement.

The main challenge with this sort of thing IME (including for button highlights) is that the original data for "what was the standard color / material / text style" gets thrown away, so you either need to generate all of the cases based on state, or cache it in another component and swap

shadow marsh
warped jolt
tame aspen
#

#1236111180624297984 should make that easy to implement though

#

Especially with the span-based design

#

Probably opt-in: picking specific text spans is relatively rare

robust solstice
#

yes

wooden drum
tulip hinge
shadow marsh
#

I bet that a lot of people try to do that though. I guess it's sort of neat if it worked, but... it feels dirty.

tulip hinge
#

I agree, we don't typically do this elsewhere, we just hadn't thought of that approach at the time

#

With respect to text picking, I have this implemented here
https://github.com/tigregalis/bevy_text_editor/blob/97194f432a44343a8763aaaa4a1b1b25c5d5ab29/src/lib.rs#L911
or here
https://github.com/tigregalis/bevy_spans_ent/blob/e520da1ab18a498bdc97e8c7ebe06540174d0e97/examples/advanced.rs#L267
for UI only.

These are almost the same, but with slightly different needs. They can easily be rolled into one. Additionally the "inner logic" can be made to be "relative to the buffer's frame of reference" so we could easily support things like 2d text: the math, without rotation at least, is very simple, but presumably that is handled by bevy_mod_picking

GitHub

Contribute to tigregalis/bevy_text_editor development by creating an account on GitHub.

GitHub

Contribute to tigregalis/bevy_spans_ent development by creating an account on GitHub.

#

effectively:

  1. what window is the cursor in?
  2. where is the cursor within that window?
  3. (translating the window to the current coordinate system) where is the cursor within the current coordinate system (UI or 2d world space, or a ray in 3d space)
  4. what entity/ies does the cursor intersect with (the parent entity)
  5. (translating to the entity's local coordinate system) where is the cursor within the entity's local coordinate system
  6. which span, under the cursor, is intersected?
#

info you could expose: window coords, current coord system coords, parent entity, local coord system coords, the span index, the span entity, the glyph

#

that same example handles interacting with specific spans (e.g. hover, hyperlinks), and spans as entities (assuming the hit detection is taken care of) makes that very easy to implement.

#

with respect to hover styles, I did have to do this:

#[derive(Component)]
struct HoverConfig {
    base_color: Color,
    hover_color: Color,
}

which sort of implies that perhaps we need a ComputedStyle (a la the web, or bevy's GlobalTransform). ComputedStyle would make things like animations/transitions easier too. the current TextStyle on a span is the computed style but we are editing it directly

tulip hinge
tulip hinge
crude tinsel
#

Regarding computed styles. There are some potentially significant memory optimisations that can be made if we add a style system to compute them:

  • The uncomputed styles can be Vec<Style> where style is an enum of style properties. So you only pay a memory cost for styles you're actually using.
  • The computed styles would probably still want to be a great big struct, but could be wrapped in Arc and deduplicated so entities with the same styles can share memory. If structs are particularly large then you can also have subsections wrapped in their own Arc
tulip hinge
true zephyr
#

Can we have a little poll maybe to see if everyone agrees with the changes?
(I will push the last few changes soon)
To recap:

  • Text does not contain ANY TextSections anymore.
  • Instead, the children of an entity with Text each have 1 TextSection now.
  • We were originally going to support having a TextSection on the parent (where the Text is) for the common case where you only need 1 section, but since we have Commands::add_child() now, that is just not worth the ugly code.

Maybe react with a 👍 or 👎 to let me know what you think, and any text feedback to go with it is nice too!

#

(based on the feedback I can start fixing up examples, writing a small migration guide, etc)

wooden drum
tulip hinge
#

that seems... not ideal

finite magnet
#

I was thinking something like this:

command.spawn((NodeBundle {
    style: Style: { display: Display:TextFlow } })
    .add_children((
       commands.spawn((TextNode("Hello, "), TextStyle::default())),
       commands.spawn((TextNode(name), TextStyle { bold: true, ..default })),
       commands.spawn((TextNode("!"), TextStyle::default())),
       commands.spawn((ImageBundle { image: image_handle })),
    ))

In other words:

  • TextNode is a component which contains a text string.
  • TextStyle is a component which specifies a text style (bold, italic, font family, etc.)
  • Text nodes are just regular entities.
  • The list of text nodes is a flat list. The template system is responsible for handling hierarchies like <b>Hello, <i>World</i></b> and generating text nodes in the right order.
  • Any UI node can have TextNode child entities, there's no special container type.
  • 3D scene nodes may also be able to support TextNode children for 3d text.
  • `TextNode entities can have non-text siblings: icons, form fields, emoji, etc. That is, you can have both text and non-text within the same parent.
  • The Display::TextFlow display style causes all of the children of the entity to be treated as a single, merged inline block, with line-breaking, word wrapping and so on.
  • Other display types (Display::Flex, Display::Grid, Display::Block) treat each TextNode child as a separate layout block - so for example if you have a Flex container with rows, each text item appears on a separate row. Word-wrapping and line-breaking still happens, but only within a single TextNode entity.
tulip hinge
#

I think these aren't incompatible

#

let's start with what we have... then next we can deconstruct the TextSection into the value and the style

#

then (imo) deconstruct the style into the individual properties

finite magnet
#

What I'm getting at is:

  • There's no hierarchy of text nodes, it's just a flat array of children.
  • Nested style spans are handled by the template layer. There are no overlapping styles.
  • Text and non-text nodes can be freely mixed (this isn't supported in cosmic-text today, but we should design the system to accommodate this in anticipation that it will be supported).
tulip hinge
#

There isn't really a deep hierarchy of text nodes, we just translated this

Text 1v0 {
  sections: [SpanA, SpanB, SpanC]
}

into this

Text 1v0 {
    children: [2v0, 3v0, 4v0]
}
SpanA 2v0
SpanB 3v0
SpanC 4v0

There needs to be a block node entity which will hold the CosmicBuffer, the block-level styles, attributes and layout info (the TextBundle), and the spans are direct (flat list of) children of that text bundle

#

If you want a deeply nested hierarchy that is up to the user or plugin author

#

so if we had something like Display::TextFlow and it's just a normal Node, we'd need to dynamically construct TextBundles

#

(add the relevant components based on the value of the Display property/component)

true zephyr
#

I think a nested hierarchy is out of scope for this PR, though we might want it later

finite magnet
# tulip hinge There isn't really a deep hierarchy of text nodes, we just translated this ``` ...

For that, I would say that a component that contains the buffer can be inserted automatically, sort of like how GlobalVisibility or GlobalTransform works. If the TextNode has a parent which is TextFlow, then the buffer component is inserted on the parent. If the TextNode parent has any other kind of display, then the buffer component is inserted on the TextNode.

How does it work with Taffy? I assume that there needs to be various hidden data structures to calculate flex layouts, grids and so on.

true zephyr
shadow marsh
#

sort of like how GlobalVisibility or GlobalTransform works

those aren't automatically inserted though. they are present because all our relevant bundles contain them.

#

not saying that's impossible, but I do think this PR is a good step while we debate how "deep text hierarchies" could work / if we even want that

#

Nested style spans are handled by the template layer. There are no overlapping styles.
I am not seeing this as a showstopper personally.

tulip hinge
tulip hinge
tulip hinge
wooden drum
tulip hinge
#

which implies we would need some form of text-based templating language built on top

wooden drum
#

Hence my bbcode experimentation

#

Of course performance is a concern though, especially when we get into interpolation of variables
Things like numbers and dates need to be localized, so whenever they change, first Fluent needs to be processed and then whatever templating we build on top of it

#

So I suppose we have to make it performant enough to be executed every frame in the worst case

tulip hinge
#

... do we fork project fluent's rust implementation

#

so we could:

  1. support arbitrary attributes on spans (fragments): i don't think we would even need to change the language:
bolded = world
    .components = Bold

message  = hello { bolded }
  1. instead of a string, generate fragments, and work with that, and only dynamic fragments need to be recomputed
[
  {
    text: "hello "
  },
  {
    text: "world",
    attributes: {
      components: "Bold"
    }
  },
]
finite magnet
#

This is what the developer writes:

<Trans i18nKey="userMessagesUnread" count={count}>
    Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>.
</Trans>

And this is what the translator sees:

"userMessagesUnread_one": "Hello <1>{{name}}</1>, you have {{count}} unread message. <5>Go to message</5>.",

In other words, the extraction pipeline strips out the style tags so that the translator can't modify them, and replaces the with numeric placeholders; these placeholders are then restored during the build using the data that comes back from the translator.

warped jolt
tulip hinge
tulip hinge
#

Ok. What's the workflow? Write your app with Trans components, extract a localisation file out of it somehow, localisers localise it, store the localisations somewhere in your app, now, given a different locale the same Trans component displays the appropriate localised text.

#

Looks like it has some compatibility with project fluent

https://github.com/i18next/react-i18next/blob/master/example/react-fluent/public/locales/en/translations.ftl

title = Welcome to react using react-i18next with fluent

description_1 = To get started, edit <1>src/App.js</1> and save to reload.
description_2 = Switch language between english and german using buttons above.

emails =
    { $unreadEmails ->
        [one] You have <1>one</1> unread email.
       *[other] You have <1>{ $unreadEmails }</1> unread emails.
    }
GitHub

Internationalization for react done right. Using the i18next i18n ecosystem. - i18next/react-i18next

finite magnet
#

I don't want to get too far ahead of ourselves here. We're not ready to work on localization or parsing of style markup just yet - the only reason for bringing this up now is that it might have an impact on the low-level text span API design. These APIs really need to provide a decent developer experience for three kinds of users:

  • direct spawning of text spans in code.
  • creation of text spans from a templating language (BSN or something else).
  • conversion of localized strings with markup in assets into text spans.
    All three of these use cases have both a static and dynamic variant. The static one - where the text is spawned once and never changes - is easy and can work with just about any API. The dynamic one is the more interesting problem.
warped jolt
#

In fluent you assign dynamic values like format!("counter-text?count={}", 5). Maybe markup tags could be assigned in a similar way.

#

Fluent resource:

counter-text = <{$1}>Count: {$count}<\{$1}>

In-code:

format!("counter-text?1=bold&count={}", 5);

Not sure if numbers can be arguments. It would also be nice to elide the extra braces and $.

tulip hinge
#

So are you saying something along these lines:

format!("counter-text?1=bold&count={}", 5);
counter-text = <{$1}>Count: {$count}<\{$1}>

->

{ "counter-text": "<bold>Count: 5<\bold>" }

->

let localized_text = fluent.localize(format!("counter-text?1=bold&count={}", 5), "en-US").unwrap(); // "<bold>Count: 5<\bold>"
let spans: Vec<(String, String)> = bevy::templating::parse(localized_text).unwrap();
for (attrs, text) in spans {
    match attrs {
        "bold" => { parent.spawn(TextSection::new(text, TextStyle { font: bold.clone(), ..default()})) }
        _ => { parent.spawn(TextSection::new(text, default()) }
    }
}
#

In examples and in-engine, should we prefer TextSection::new(TEXT, default()) or TextSection::new(TEXT, TextStyle::default())?

wooden drum
#

I am thinking of something like this:

  1. User creates a component like
LocalizedText::message("counter-text").with_arg("count", 5)
  1. Based on a Locale resource, the corresponding asset file is selected automatically
  2. The asset file is parsed and the corresponding UI hierarchy is constructed. In particular, the count arg has it's own TextSection with a marker component. (This would likely require us to fork/hook into fluent-rs)
  3. The user can dynamically update LocalizedText , e.g. update the count arg. The localization system will then use ECS queries as usual to update the corresponding TextSection efficiently.

The advantage would be that dynamic updates don't require us do parse everything again, we can just use queries.
The disadvantage is that this is a lot more complicated to "get right", especially with the formatting functions and selectors coming into play

#

But probably something we need to tackle incrementally

warped jolt
#

There’s also an issue of duplication of logic for setting args at construction vs update sites.

warped jolt
wooden drum
#

Although we should avoid changing the API too much. Until we have higher layers that build on the lower level text API, every change will create a high amount of churn for the users

warped jolt
#

If we go for a markup language, then I think we’d expect users to write out single strings anyway which are split into spans behind the scenes. (and only manually write to spans at a lower level)

wooden drum
warped jolt
wooden drum
warped jolt
#

I designed bevy_cobweb_ui’s localization based on current Text ^

wooden drum
#

Ah, right.
I'm operating under the assumption that we will get spans as entities and TextStyle as component

warped jolt
#

Even if those changes are made, I don’t right now see bevy_cobweb_ui’s API being improved. The TextEditor system param makes text editing immensely easier.

#

However relying on it does lock you into some limitations, since TextEditor internally has a mutable query. So you can’t query Text in the same system without a ParamSet.

#

Which would become even worse if Children come into play.

#

It’s very likely we need markup for localization, which begs the question if markup should also be a mainline feature for non-localized text. The problem I see is how to support both markup text (which gets parsed automatically into spans, and unused spans are deleted) and manual text (where users fully control their own text). I think we’d need an additional MarkupText component.

warped jolt
#

Relying on change detection, I think you’d need to set and store the requested font in LocalizedText.

#

Another problem: if you want to animate text color on a specific subspan of a localized/markup text, how do you identify the right section/child entity? I think the markup language would need tags of some kind.

wooden drum
warped jolt
#

But I still think storing args separately from the template is not ideal.

wooden drum
#

(Right now the "tree" is flat though and only has text as children)

wooden drum
#

It does change the layouting though, we probably need the inline box layout for it to work correctly

warped jolt
wooden drum
warped jolt
#

Sorry I still need to look at your repo properly

wooden drum
#

Right now its quite unoptimized though and never creates multiple sections in a text, I focused on functionality over performance for now

wooden drum
tulip hinge
#

@true zephyr in relation to this:
https://github.com/bevyengine/bevy/pull/14572#discussion_r1705331294

Within bevy_text::TextPipeline::queue_text():

  1. section_index is born in bevy_text::TextPipeline::update_buffer(): (sections.iter().enumerate())
  2. copied into cosmic_text::Attrs::metadata in get_attrs(...)
  3. cosmic-text copies it into cosmic_text::LayoutGlyph::metadata during buffer update/creation
  4. copied into bevy_text::PositionedGlyph::section_index for later use

Since section_index is no longer really used as an actual "index" (just as a key to reference the section), it can just be a "metadata" field (we should perhaps even rename it).

You could just "recreate the conditions" at usage so that the index means the same thing as they did at creation, but the most robust solution is probably to create the "index" from outside TextPipeline and pass it in:

  1. enumerate all the Children of the Text to get the "Children index", i.e.
text_section_query: Query<Option<Ref<TextSection>>, With<Parent>>,

sections.clear();
sections.extend(
  text_section_query
    .iter_many(children)
    .enumerate()
    .filter_map(|(index, maybe_section)| maybe_section.map(|section| (index, section)))
);
  1. pass in the &[(usize, Ref<TextSection>)] to TextPipeline::queue_text() instead of &[Ref<TextSection>] (or you could potentially just pass the iterator directly, and you won't need the sections scratch buffer)
  2. use this index through metadata and ultimately into PositionedGlyph
  3. now text2d_section_query.get(children.unwrap()[*section_index]).unwrap() should be fine (sort of... that section could have been despawned between updating the text layout and drawing the text, perhaps throw a warning, and skip)
tulip hinge
#

The other idea, that was discussed recently, was to use the entity itself as the metadata... but:

  1. if we take the entity.index() as usize as the metadata (u32 as usize), we wouldn't even need the parent to retrieve the data (but we can always get to the parent from the child's Parent component if required), but I've no idea how to get back to an entity from the index (doesn't seem to be supported), and strictly speaking that section could have been despawned between updating the text layout and drawing the text
  2. if we used entity.to_bits() as usize and later Entity::from_bits() these are 64-bit numbers that may not be compatible on all (i.e. 32-bit) targets
  3. alternatively we could create a global resource HashMap<usize, Entity> or just Vec<Entity>, and we use that to look up the actual Entity.
#

Actually this

section could have been despawned between updating the text layout and drawing the text
could be an issue with all the approaches. Generally between running the update_text_layout systems and the rendering of the text, users shouldn't be messing with the hierarchies, but they could.

#

Well, I guess it's no different from today - users could mutate the Text component between running the update_text_layout systems and the rendering of the text

#

So maybe I'm overthinking it

shadow marsh
# tulip hinge The other idea, that was discussed recently, was to use the entity itself as the...

re: 1, yeah, I don't think it's appropriate to do an entity -> index -> entity conversion at runtime.
re: 2 yeah, we probably shouldn't do this. Would be totally fine if we could convince cosmic-text to make metadata a u64, I think.
re: 3 I may be wrong but this doesn't feel much better than getting the entity from Children.

So maybe I'm overthinking it
yeah, probably. though having friendly warnings when this happens would be ideal though

little patrol
#

is this a cosmic text help thread? 🙂
what's the best way to embed a square sized icon inside a cosmic text layout_run with an arbitrary font? (on the layout level, rendering is a custom implementation)

tulip hinge
little patrol
#

wait no I guess there's kerning

tulip hinge
#

You could do the "embedded font" thing like Bevy uses for its default font.

#

And that font could specifically select exactly one glyph.

#

(There's a proper term for this, I forget what it is)

#

Subsetting I think

little patrol
#

that's a great idea 🩵

tulip hinge
#

It'd be easiest either to find some font with an exactly-square glyph, or make that font yourself with just that one glyph.

little patrol
#

cute lil 2kb font

tulip hinge
#

Apparently an "em dash" character is what you want perhaps

#

Supposed to be exactly 1 em wide

#

I suppose if you got your custom single-glyph font it doesn't matter though

tulip hinge
true zephyr
#

Ok, sorry for not really being active, but I can get back to the PR now. can someone give me a recap of what should still be changed?

#

(other than fixing examples)

tame aspen
#

(I'll take a look at this in a couple hours if no one beats me to it)

shadow marsh
finite magnet
#

Once we get text entities and font families out of the way, I'd like to discuss a very basic editing API. Doesn't need to be very elaborate. The idea is to enable third-party crate authors to implement text input widgets. As a third-party crate author, I would expect to be responsible for updating the text entities when the user types a key, drag states, insertion/deletion, undo and so on. What the Bevy API should provide is:

  • picking: takes pointer coords as input and returns the character index of the nearest glyph hitbox. Both box-style and i-beam style picking should be supported. If the coords are outside of the text block, it returns the nearest result.
  • selection: given a (start..end) range of character indices, returns a list of rectangles representing the area of the selected glyphs. If the text is bidirectional there may be multiple rectangles returned.
    I presume cosmic-text already has functions like this, so potentially these APIs can just be thin wrappers around what's already there.
crude tinsel
# finite magnet Once we get text entities and font families out of the way, I'd like to discuss ...

I would expect to be responsible for updating the text entities when the user types a key, drag states, insertion/deletion, undo and so on.

While I feel like text-as-entities is generally a good model, I feel like you're doing things in hard mode if you implement editing like this. If you just have the "text input" field be one entity that contains it's own state then you can use Cosmic-Text's entire editor abstraction which has the following available actions: https://docs.rs/cosmic-text/latest/cosmic_text/enum.Action.html

finite magnet
# crude tinsel > I would expect to be responsible for updating the text entities when the user ...

OK, that could work. And it doesn't seem so specific to CT that you couldn't make something similar for Parley if we decided to switch later. It should be possible with an API like that to implement "controlled" (in React terms) widgets - that is widgets where the reactive framework decides when a mutation happens, as opposed to the native widget doing all mutations internally and the reactive framework only getting notified after the fact. The fact that controlled text inputs actually work in the browser seems like a bit of an accident, and I can well imagine that if we're not careful in our design we might foreclose that possibility; this would mean that only "uncontrolled" text widgets were possible for third-party devs to implement.

#

For those not familiar with this:

  • Controlled widgets are ones where the application code is part of the editing loop for the widget, meaning that the app code listens to input events and decides whether or not to update the widget state.
  • Uncontrolled widgets are ones where the native widget is entirely self-contained, and the app code can only (a) initialize the widget state to some value, and (b) get notifications after the fact when the widget updates itself.
#

Many developers prefer controlled widgets, as it makes it a lot easier to implement things like form field validation, restricted numeric inputs, and so on.

#

Uncontrolled widgets also have this weird ambiguity because sometimes you need to change the widget state programmatically, but the widget is also changing its own state programmatically, so you have to be careful not to cross the streams.

#

React supports both styles, which is a nice flexibility.

#

The simplest example is a checkbox: in the controlled case, there's only one source of truth, which is some state variable in your app; the checkbox never sets its own state, but instead emits an event which tells your app to update its state, and this in turn causes the checkbox to redraw in response. In the uncontrolled case, the checkbox sets its own internal checked state, and then sends an event telling the app that something changed; this means you potentially now have two source of truth which have to be kept in sync.

#

All of the widgets in quill/obsidian are controlled; for example, the open/closed state of a dialog is a parameter which is passed in from the outside, not internal private state.

#

So how this relates to text is that I'd like the option to be able to write a controlled text input widget.

true zephyr
true zephyr
#

I am seeing a pattern that the following is REALLY common:

commands.spawn(TextBundle::default()).with_child(TextSection::new(...));

Maybe we need a follow-up PR where we add some form of shorthand for this

twilit iron
#

Actually think this should be solved right away, because adding a PR that increases ergonomics, that decreases ergonomics is a bit of a pain.

But then again, adding things with children is generally speaking not very ergonomic in bevy right now

true zephyr
tulip hinge
true zephyr
tulip hinge
#

I think bsn / required components / EntityPatch might cover the ergonomics side of things, but if we were to do a follow-up PR, maybe an extension trait for Commands so you could do something like commands.spawn_text.

shadow marsh
#

I agree / think the way to make things actually happen is probably to leave it as is for now. Ergonomics of spawning a bundle with a child will be a bigger discussion depending on how much of cart's plans for scenes make it in before 0.15.

true zephyr
#

I could just collect it, or do a .cloned(), but that doesnt seem ideal...

#

can anyone help me out with this? this is like the last thing before the PR is actually done

tulip hinge
#

What error do you get when trying to store it as a local?

true zephyr
#

Ill send it one sec

tulip hinge
#

ah, right... sections lives outside the system / across time, and Rust can't tell that we throw away the contents of it before the end of the system

true zephyr
#

what should we do instead?

tulip hinge
#

It's starting to get clunky, but because it's an immutable query, you can safely construct an iterator more than once:

            let sections_first_use = section_query.iter_many(children.iter());
            let sections_second_use = section_query.iter_many(children.iter());
            let sections_third_use = section_query.iter_many(children.iter());
#

There's probably a cleaner solution than this though

#

maybe you can just pass the section_query and the children as references into queue_text

#

then queue_text can construct the iterator as needed

#

Either way would get you around the issue of only being able to use an iterator once

#

But hopefully someone else has a better idea

true zephyr
#

yeah I was also thinking of just passing that query to the pipeline, but it just seems messy

#

should I just mark the PR as ready for review, to see if anyone has a better idea?

tulip hinge
#

I guess so, maybe just leave a comment in the description in relation to the allocations

twilit iron
# tulip hinge I think perhaps the examples may only show the extra ceremony, but don't as ofte...

Yeah this, it irks me a bit when we are adding something to increase ergonomics, but what is shown mostly seems like a decrease in ergonomics...

Which I agree is much because we are banking on the BSN thing to solve things.

The extension trait is a potential solution to this, since I really don't want to end up in a 0.15 release where text is getting less ergonomic for most cases.

And changing all the examples to a new ergonomic with an extension trait is probably quite boring and hard to find volunteers to do.

true zephyr
#

Well to be fair, I just sat through updating practically every example lol

#

not doing that again

twilit iron
#

Fully understandable, which is why I'm afraid to decrease ergonomics for all users, and not having any solution/volunteers wanting to fix it

tulip hinge
#

One change that will reduce the total number of allocations @true zephyr

#

construct the sections outside the for loop

true zephyr
#

and trust me, right now it already is an increase in ergonomics for every single non-trivial text application, because you dont work with raw indices into a sections array anymore

twilit iron
#

Oh I get that, I am just of the belief that most things made in bevy are trivial text applications

#

As in single section texts being the norm

true zephyr
#

that is true, we will need some way to do that in a good way

tulip hinge
#

sections is at least allocated once per frame rather than once per text

twilit iron
# true zephyr that is true, we will need some way to do that in a good way

Yeah agreed, I rather the api break happens once, not twice though.. Basically I don't feel we have an equivalent to Text::from_section() in this PR.

And I'm not sure, but I think this is the first occurrence where we take something and force a parent/child hierarchy even in its basest form.

We lose a lot of ergonomics for the most common case, to gain ergonomics in other less common cases. Even if we all agree that this is the path to take, we shouldn't really introduce more papercuts in adding text.

true zephyr
#

true, we probably should make sure we land another PR that fixes this is ASAP. if we can agree on an approach to take soon, I can add it to this PR.

true zephyr
shadow marsh
shadow marsh
#

If you want, but it won't make a whole lot of sense until your PR gets merged.

#

That's just my opinion anyhow.

true zephyr
#

I'll just open it, otherwise ill just forget about it

shadow marsh
#

But I think the bikeshedding is gonna be intense with or without bsn, and we shouldn't delay this work / let it rot while we sort through that.

twilit iron
#

yeah, I am not entirely convinced that bsn will land before 0.15, and dont wanna put all the eggs of solving creating a simple text on that

shadow marsh
tulip hinge
shadow marsh
#

opinion: I personally don't feel like it is the time to rename to Span. There seem to be two concepts associated with the word:

"an html span": a nestable/styleable container for text data or other spans or other objects undergoing "text layout"
"a cosmic-text span": a styleable (possibly overlapping?) range of indices into text data

and what we have now is not either of those things, although they are very related to cosmic spans. I think they are most accurately described as sections. non-overlapping stylable chunks of text.

tame aspen
#

Hmm, okay

true zephyr
#

can someone give me a quick list of what I still need to do, there has been so much mentioned I lost track. (I really want to get this done soon, I have already spent a lot of time on this)

robust solstice
#

Yeah, I’d also like an update on the state of this group

shadow marsh
#

Hmm, okay

If the goal is to give users a clean break from the way we previously did text by giving these new things a new name and thus preventing confusion, then I think it's better done later. Because these TextSections are the same thing as the old ones, they are just stored in a different place. We haven't really changed anything, IMO and renaming would not accomplish that goal.

tulip hinge
#

They are used differently to the old way, because of the hierarchy requirement

shadow marsh
#

Yeah, I get that. I don't feel super strongly about this. I also just want to keep this moving and I don't want to pile more work onto Preon.

tulip hinge
#

in any case I think TextSection may "disappear" soon

#

if we're talking about turning it into a bundle

shadow marsh
#

But if they are going to be changing conceptually into an "html span" or "cosmic span" later then I think we should rename them at that time

twilit iron
#

As I see it:

  • Rename the parent node to the suggested textlayout or something
  • Fix docs
  • Create follow up issue for breaking apart text section
  • Create follow up issue for renaming textsections
shadow marsh
tame aspen
#

Thank you both 🙂 @true zephyr those all seem very reasonable. Let us know if you'd like a hand on the implementation side!

true zephyr
#

Oh awesome, ill take a look soon! (Probably tomorrow, i have had a long day)

mental tree
#

I think its worth discussing the ergonomics issue of:

TextBundle::default().with_child(TextSection::new("Hello", TextStyle::default()))

Lets pretend first that TextStyle is broken out into a separate style component, as I think thats very likely to happen / in line with the general direction we're headed in:

TextBundle::default().with_child((TextSection::new("Hello"), TextStyle::default()))

I personally don't think BSN on its own is the solution to this problem, although it does help. With Required Components + BSN, it would look like this:

Text [ TextSection::new("Hello") ]

Text (just a marker component) will pull in the remaining components in TextBundle. TextSection would require TextStyle, so specifying it directly is no longer required.

That being said, in the world of UI, this is still "too much boilerplate" for simple text scenarios. In general the "BSN way" to improve ergonomics is not to build new abstractions / builders ... it is to improve the core data API (I've seen some people discussing the abstraction-building path above).

People will want to be able to write text like:

Text::new("Hello")

So I think the only question is "how do we make that happen". For example, is it possible to make floating / standalone TextSections work as expected? Can we make the "manager parent" pattern optional?

#

Another way to put this is "if it feels bad without BSN, it will probably also feel bad with it, just to a lesser degree". Ex: with Required Components, but without BSN, it would look like this:

commands.spawn(Text::default()).with_child(TextSection::new("Hello"))

The core issue here isn't a syntax or "scene system capabilities" problem. Its a data-relationship-complexity problem

tame aspen
#

This is something that I'm concerned about too. I'd really like to make spawning simple text simple

#

It does introduce a fair bit of implementation complexity to be able to support parentless text sections like this, but the UX is worth it IMO

mental tree
#

As a thought, can we make TextSection be the "one" text entity type (i'll rename it to Text for simplicity), with support for hierarchies?

Ex:

// This gets flattened into a single layout object
Text::new("Hello") [
   Text::new("World"),
   Text::new("!"),
]
tame aspen
mental tree
tame aspen
#

Yeah okay, I'm down for that 🤔

mental tree
#

Internally the top-level Text would behave as if it were the "grouping text entity" we have today

#

And it could store that on some generated component

#

I'm not dictating this as a solution ... I haven't been in the weeds of Cosmic Text so feel free to tell me how this might break that model

#

Just trying to imagine one potential world where people will be satisfied with the ergonomics

robust solstice
#

could we special-case strings without type annotation to resolve to Text? This would get us a lot closer to the clean look of html.

mental tree
robust solstice
#

Setting that aside, I think I pushed for something similar to this a few months ago, and am on board with the single text component.

mental tree
#

I'd prefer to not paper over this with sugar, as implicitly creating entity hierarchies for "Hello" feels hard to grok

shadow marsh
mental tree
robust solstice
# mental tree As a thought, can we make TextSection be the "one" text entity type (i'll rename...

I am pretty sure we won't want to do this, but it's worth mentioning that we could make these more like s-expression by moving the first brace up

[
   (Text::new("Hello"), TextStyle { color: Color::RED })
   Text::new("World")
   Text::new("!")
]

could translate into a tree as before. Maybe thats too much of a conceptual shift, but it more closely follows how I would expect this sort of thing to be layed out.

finite magnet
#

The way this works in Quill currently is that String and &str both implement the View trait (this idea is stolen directly from Xilem), so you can pass in "Hello" any place you can pass in a widget:

Button::new().children((
    "Cancel",
    Icon::new("cancel.png")
))
mental tree
robust solstice
#

right i'm suggesting [ parent | children... ] vs parent [ children... ]. Is there any drawback to changing where the hierarchy split happens?

#

just curious really, not pushing for this change. Feel free to ignore if this is obviously unwanted.

mental tree
#

Which feels like a pretty big downside

#

I think we'd need much clearer syntactic distinctions if we embraced a flat list

robust solstice
#

idk, maybe just me but it makes the ordering look more explicit and matches the form i generally expect from lisp/html. in the current impl the parent is in the place where I would expect a 'tag' to be in markup languages, but it's semantically very different from a tag.

finite magnet
#

The ergonomic "vision" that I had for text was something like the following:

  • If the text node is a child of an entity with Display::Flex, Display::Grid or Display::Block, then each text item is treated as a separate "cell" or "box" and laid out in its own grid cell or row/column. This means that the cosmic-text layout is performed on that single entity.
  • If the text node is a child of an entity with Display::Flow (a new display type), then the cosmic-text layout is performed on the parent, with all the children (both text and non-text) being part of the flow. This would mean that Bevy would need to automatically insert whatever components necessary to maintain the cosmic-text state.
#

So Display::Flow behaves something like a paragraph.

mental tree
finite magnet
#

So in the button example, the button is Display::Flex, with FlexDirection::Row, with a small Gap. The text label and the icon are arranged in a horizontal row.

mental tree
#

It also means you could make any node a "parent text layout manager", which feels nice

shadow marsh
#

The previous implementation of

commands.spawn((TextBundle::default(), TextSection::new("")));

Involved a bunch of special casing around text section enumeration and the hierarchy. Automatically converting this sort of thing to an entity with one child when spawning seems much more reasonable, though surprising in its own way.

robust solstice
#

(nevermind me [[X Y] Z] == [X Y Z] in the alternate format, this seems undesired)

mental tree
finite magnet
#

Styling: In Quill, the automatic conversion of String/&str doesn't allow for the insertion of style components, but you can explictly call TextStatic::new("string").style(...). Most text styles are specified through inheritance - in my framework, text styles are the only inheritable styles, because it's so tedious to assign a font handle to every text span. This inherited styling is a separate system, independent of Quill.

shadow marsh
#

Ah, sorry, just understood your previous message on that topic.

mental tree
finite magnet
mental tree
shadow marsh
#

What would this do?

Node [
   Text::new("World"),
   Text::new("!"),
]
finite magnet
# mental tree Reasonable

In fact the only real difference between Display::Flow and flex-wrap is how it treats adjacent text spans.

mental tree
finite magnet
#

Actually that's not the only difference, sorry

shadow marsh
#

Yeah. I guess that is not too confusing.

finite magnet
#

Effectively, Display::Flow would merge adjacent spans, and then allow the line-breaks to be within a single merged span, according to whatever the cosmic-text layout calculation produces.

#

This is not much different than the behavior of display: block in CSS.

#

Although I'm not advocating that we support explicit line breaks

mental tree
finite magnet
#

What I'm going for here, BTW, is not to regurgitate the web. I'm going for the quest log / character bios in Witcher 3 or Jedi: Last Survivor 🙂

mental tree
#

Anyone want to put together a quick design doc for this? Aka describe the new set of components, how they relate to each other, Display::Flow, and how this ties into the details of Cosmic Text layout?

#

I can do this, but my plate is already overflowing so it would take some time

finite magnet
#

I could, but I'd like to see someone else's take on this. If no one else volunteers, I will.

mental tree
mental tree
#

~a day or so

finite magnet
#

Sure. I can also help polish the prose if that's needed.

finite magnet
shadow marsh
#

Yeah, my brain is crumpling slightly under the load of trying to figure out how that will work

finite magnet
shadow marsh
#

It seems pretty likely that we would want to bug cosmic about u64 metadata so we can just store entities in there. At least I think that would probably simplify things.

mental tree
#

Combine that with detecting when that isn't the case for a given Text, and treating it as flat / adding that to that entity

#

Again, not saying its the path to take, but seems to map approximately 1:1 to the impl in the TextSections-as-entities PR

shadow marsh
#

Does this seem like an accurate representation of things-we-want-to-be-able-to-do in non-bsn (but w/ required components) terms?

// 1. [Hello]
commands.spawn(Text::new("Hello"));

// 2. [Hello, World]
commands
  .spawn(Text::default())
  .with_children(|parent| {
    parent.spawn(Text::new("Hello"));
    parent.spawn(Text::new("World"));
  });

// 3. [Hello, World]
commands
  .spawn((Node, Style { Display::Flow, ..default() }))
  .with_children(|parent| {
    parent.spawn(Text::new("Hello"));
    parent.spawn(Text::new("World"));
  });

// 4. [Hello, World]
commands
  .spawn(Text::new("Hello"))
  .with_child(Text::new("World"));

// 5. [H, ello, W, orld]
commands
  .spawn(Text::new("H"))
  .with_children(|parent| {
    parent.spawn(Text::new("ello"));
    parent.spawn(Text::new("W"))
      .with_child(Text::new("orld"));
  });
#

And do you see that situation as an acceptable intermediate target?

finite magnet
tame aspen
#

Yeah, I don't care about the last example

#

The rest looks useful though!

finite magnet
#

Actually I should say the last two examples

shadow marsh
mental tree
shadow marsh
#

And that seemed to imply that the last was possible too, but are we limiting to that one special case?

mental tree
#

In my "unified Text" proposal, it would have walked up until it found a root Text entity

#

(at least, thats what it was in my head)

finite magnet
#

Given that this is ECS, nothing prevents you from giving a text node a child - but for my purposes it can just be "undefined behavior" or not supported.

mental tree
#

I think in the context of Display::Flow, it is desirable for the last two examples to resolve using the algorithm we already defined

#

Aka they would use the default Display / be treated as standalone text sections

finite magnet
#

BTW, another thing I like about Display::Flow is that the extra cost of layout is explicit - that is, users who don't use it don't pay the cost.

#

We want the ergonomics to be as simple as possible - but not so simple that they hide important consequences from the user.

#

@mental tree Do you have any thoughts on @shadow marsh 's comment about needing u64 ids in cosmic text? This seems like a potential roadblock, or at best a delay.

shadow marsh
#

I don't think it's a blocker or anything

mental tree
#

I'd want to have a clearer understanding of what that buys us before using political capital to drive that change upstream

shadow marsh
#

But we are using "section indices" now in cosmic metadata because it's a usize. If we can still get a consistent section index while traversing the hierarchy then it's no problem.

mental tree
#

Unless the cosmic "section index" is decoupled from ordering

shadow marsh
#

section index is entirely on our side, stored in a generic metadata field for each span in the cosmic buffer so we can map back and forth.

#

what gets ugly is when users want to do stuff with the cosmic buffer, and they now need to find the associated entity. but all they have is that section index we made while traversing the hierarchy.

#

when it was just one list of children, it was very easy for users to look up

#

but now that it's an arbitrary hierarchy, it sure would be convenient to store an Entity in the metadata instead.

#

actually now that I have typed it out this seems like it may be a problem

#

guess we could maintain a map component as a convenience

finite magnet
#

Maybe only create the map when users use those apis? Or require the user to create the map explicitly when they start using the cosmic-text buffer?

shadow marsh
#

There will be in-engine uses too

#

What about Text2d? I suppose that needs to be

// 1
commands.spawn(Text2d::new("Hello"));

Or some sort of variation of

// 2
commands.spawn((Text::new("Hello"), Text2d));
finite magnet
#

Hmmm ok. The main use case I know of is the text input widget, and those will typically be written by library authors - and I don't mind putting extra work on library authors if it reduces the load on the average user.

shadow marsh
#

If we're already traversing the hierarchy once and building the indices, we might as well store a map and just make that "convenient" for everyone. It would still be slightly obnoxious to look up entities, but you're right that an average user probably won't have to deal with it.

shadow marsh
crude tinsel
#

IMO the best way to think about Display::Flow (which the web calls display: inline - flow refers to the combination of block and inline layout) is that it's just text layout, and child boxes/inages/widgets are essentially laid out as if they are a single giant glyph.

tulip hinge
true zephyr
#

Hey, I woke up a bit sick, not sure if I have it in me to finish up the PR today. I would really appreciate if someone could rename Text to TextLayout (and make the needed updates) for me, and update the docs accordingly.

tulip hinge
#

(or vice versa)

shadow marsh
#

Okay, so if I'm caught up correctly, we decided that

// 4. [Hello, World]
commands
  .spawn(Text::new("Hello"))
  .with_child(Text::new("World"));

Was perhaps not important / undesired.

But how do we reconcile that with another example from that code block:

// 2. [Hello, World]
commands
  .spawn(Text::default())
  .with_children(|parent| {
    parent.spawn(Text::new("Hello"));
    parent.spawn(Text::new("World"));
  });

?

Is that not allowed / should be written w/ the text as children of a Display::Flow node instead?

shadow marsh
#

Canonically, text nodes are leaves of the tree. Any children of a text node are ignored by the layout.

Okay, so it seems that example 2 was also considered invalid. That is what has been confusing me, but that should have been pretty obvious, so my bad.

crude tinsel
# finite magnet I'm not sure the last example makes any sense, I tend to think of text entities ...

IMO the HTML/CSS model of text is bad in that it doesn't allow you to explicitly specify "block" or "inline" layout on a parent node. It only allows you specify "flow" (= block or inline), and then determines the layout mode automatically based on the contents, which is both expensive and confusing.

IMO it would be much better to have an explicit inline display mode, which is what things like React Native do with their Text component.

shadow marsh
#

I do spend too much time in RN which might have added to my confusion ^^

crude tinsel
#

For those who are not familiar, React Native uses a Text component both for creating a "text layout context", and for "spans" within that context.

I'd personally lean towards having these be separate ecs components, but IMO the model of a dedicated component for a text context is a good one.

finite magnet
#

Now, whether this is handled in the widget, or lower down, I feel less strongly about. (Although handling in the widget is easier to do in JS than in Rust).

shadow marsh
#

Does this now seem like an accurate representation of the proposal?

// 1.
//
// Buffer [Hello]
commands.spawn(Text::new("Hello"));

// 2.
//
// Node
// - Buffer [Hello, World]
commands
  .spawn((Node, Style { Display::Flow, ..default() }))
  .with_children(|parent| {
    parent.spawn(Text::new("Hello"));
    parent.spawn(Text::new("World"));
  });

// 3.
//
// Node
// - Buffer [Hello]
// - Buffer [World]
commands
  .spawn(Node)
  .with_children(|parent| {
    parent.spawn(Text::new("Hello"));
    parent.spawn(Text::new("World"));
  });

// 4. (Invalid. Child node ignored. Possibly, a warning is raised.)
//
// Buffer [Hello]
commands
  .spawn(Text::new("Hello"))
  .with_child(|parent| {
    parent.spawn(Text::new("World"));
  });
finite magnet
# shadow marsh Does this now seem like an accurate representation of the proposal? ```rust // 1...

I typed up a more formalized specification here: https://github.com/bevyengine/bevy/issues/14854#issuecomment-2308535983

GitHub

What problem does this solve or what need does it fill? With the changes introduced in the PR linked below, the common case for Text where there is only one TextSection gets a bit more annoying. Th...

shadow marsh
#

I saw

#

Looking for an understanding check

finite magnet
#

One open issue is: how the various entity style properties - margin and so on - map to cosmic-text features for inline spans, if at all

#

If the answer is, "they don't" that's acceptable, but should be documented.

#

obviously there are some obscure edge cases - what happens to a ligature if the unicode chars are in different spans, and they have a non-zero margin. But these don't need to be part of an MVP.

crude tinsel
finite magnet
crude tinsel
#

That's true. I'm a little concerned about performance with that approach, but we'll see.

#

Another option is a Style component containing a Vec<StyleProperty>

finite magnet
finite magnet
# crude tinsel You didn't like it?

It's not so much that I didn't like it, but other people on discord didn't like the other baggage that came with it - CSS-style selectors for dynamic styles and so on.

#

Instead, I went with a "styles are functions" approach.

crude tinsel
finite magnet
#

With styles-as-structs, you have some upper layer which can be Vec<StyleProp> or a function, and this gets applied to the struct in some defined order, so the composition becomes simply the order in which the mutations happen. But if the style is itself a vec, now the only way you have to compose things is concatenating (and possibly de-duping) the vectors.

#

Composition is used not just for theming, even basic tasks like "I want a standard button, but with this margin".

crude tinsel
#

What Servo/Firefox do is have a computed struct which is not a Vec (but which uses Arc for deduplication).

#

You could potentially combine that with a Vec<Vec_>>

finite magnet
#

By "standard button" I'm talking about some kind of opinionated reusable button, not a raw unstyled button.

finite magnet
#

Sparse representation + serialization is the key to both editing and composition.

#

Back to the topic at hand: if a proliferation of style components is a concern, perhaps we could split Style into 2-3 components, grouped by some logical criteria.

#

Although naming would be a challenge

#

"The two hardest problems in programming are naming, cache invalidation, and off-by-one errors."

#

Also, TBH things like margin and padding are almost never used for inline elements in web apps. If you want to add space between paragraph lines, use leading. Background color is used for highlights, of course, and sometimes you see outlines, but only as an overlay. So perhaps the best answer is to simply not support properties that are inconvenient.

#

One of the most massively productive things a software engineer can do is recognize when a problem doesn't need to be solved.

warped jolt
#

I think an MVP for v0.15 doesn't need Display::Flow

shadow marsh
#

That seems quite reasonable to me and deals with some of the head scratching I was doing when I was imagining an implementation. IMO TextNode should do text / flow layout of children by default. And if we don't implement "flow"ing non-text things, we don't need an explicit Display::Flow.

warped jolt
#

It’s pretty unclear to me what Display actually does to a text node, whether Flex/Grid/etc.

finite magnet
#

Display::Flow isn't just about mixing text and non-text items. It's also about how adjacent text nodes are line-wrapped. If you have multiple text entities in a flexbox with row layout, even if that flexbox has flex wrap enabled, it's going to always wrap at the entity boundaries. With flow, the adjacent text entities are treated as one long string, so wrapping can happen anywhere (well, on a word boundary anyway).

warped jolt
#

I guess it affects whether Style fields related to Flex/Grid will be activated or not.

warped jolt
#

And using TextLayout to control linebreak/wrapping.

finite magnet
#

It would be easier to explain with an illustration

#

Let's put aside the issue of style for a moment and just talk about what happens with plain text. In a flexbox, each text node creates a separate "flex cell". Those flex cells are laid out in a row (or column), and their size is calculated based on the calculated min/max width of the cell. For text nodes, the max width is the width of the string; the min width is, I don't know, something small I think. If the total width of the text is larger than the available flex space, the the text string gets word wrapped, but only within the flex cell - it doesn't affect the wrapping of neighboring cells.

Now, flexboxes also have a "wrap" feature that allows the cells themselves to wrap to the next line. However, this never splits a cell, the entire cell always goes on a single row.

#

So if I have two long strings that are adjacent to each other, and each one is a text node, if I put them in a flex row, either one of two things will happen: either the flexbox will wrap each string separately, and then put the wrapped lines side-by-side; or it will put the second string on its own row (if flex wrapping is enabled).

#

Neither of these is the intutive thing that you would expect, which is to join the two strings together in a single string, whose wrap point is then calculated based on the width of the joined text.

#

For something like a table or a toolbar, the existing flex behavior is exactly right. But it's wrong for something like a paragraph.

warped jolt
# finite magnet Neither of these is the intutive thing that you would expect, which is to join t...

Right, so my question is what different results you'd get from these:

/// 1. Display::Flex (implicit)
commands
  .spawn(TextNode)
  .with_children(|parent| {
    parent.spawn(Text::new("Hello"));
    parent.spawn(Text::new("World"));
  });
/// 2. Display::Flow
commands
  .spawn((TextNode, Style{ display: Display::Flow, ..default() }))
  .with_children(|parent| {
    parent.spawn(TextNode::new("Hello"));
    parent.spawn(TextNode::new("World"));
  });
#

And if you have Display::Flow, what happens to the Style components on child nodes?

#

Honestly I'm a bit puzzled if you are suggesting the child nodes would be separate TextNodes, or separate Text (i.e. text spans) under a TextNode that has Display::Flow.

finite magnet
finite magnet
crude tinsel
finite magnet
warped jolt
finite magnet
#

In the simplest sense, Display::Flow is just a way to build a paragraph out of multiple text entities.

warped jolt
#

So the deeper question here is if there's more fine-grained settings on text construction that aren't captured by 'collect all the contiguous text in this hierarchy'.

finite magnet
#

You need somewhere to put the buffer

warped jolt
#

Or maybe a better way to put it: are there special settings for UI text assembly that wouldn't be present for 2d?

finite magnet
#

I'm coming at this from the other end, which is: for a given templating system (sickle_ui, quill, BSN) what kind of experience do we want to give the user - and then work backwards from there.

#

I'll use quill/obsidian for an example. The Button widget has flexbox layout - that is, if the button has multiple children they are laid out side-by-side, with a small gap in between them.

#

Most buttons will have a single child which is a text string. But buttons sometimes have a string and an icon - like a cancel button with a cancel icon.

#

So in Quill, you can say either:

Button::new().children("Cancel")

or

Button::new().children(("Cancel", Icon::new("cancel.png")))
#

Now, we can use various tricks with Rust .into() to automagically convert that text string into a TextNode. So really what we are saying is:

Button::new().children(TextNode::new("Cancel"))
crude tinsel
#

Would it be possible to have:

Button::new("cancel")

And have Button deal with setting the children?

finite magnet
#

The question then becomes: does the .into() magic, or templating system or whatever, need to understand that there are multiple flavors of text nodes, which are context-specific? That is, depending on where you pass in the text string, it gets converted into a different kind of node? Conversely, do we just get rid of the magic and make the user always wrap the text string in the correct kind of text node for the current context?

warped jolt
#

I'm not sure I know what you're getting at by context-specific text nodes/text node flavors. As in 2d vs UI?

warped jolt
# warped jolt It sounds like you want `Button` to 'auto-detect' when its children are text spa...

It should be possible to implement this without Display::Flow. Add a marker component to Button to detect when its children include a Text component, then insert TextNode to the button entity (or its content sub-entity where children are added). Using Display::Flow would have the same effect (you'd need to poll for Changed<Style>, which is probably less efficient). That is, unless I'm misunderstanding and there's additional magic/complexity around how Display::Flow would affect nodes.

The magic/complexity of Display::Flow kind of puts me off. It seems to add ambiguity to an already opaque UI API surface.

shadow marsh
#

With flow, the adjacent text entities are treated as one long string, so wrapping can happen anywhere (well, on a word boundary anyway).

Not saying we "don't need flow" just that "we already have flow and don't necessarily need Display::Flow if text is the only thing we're laying out. It could just be implicit behavior of children of a TextNode like how the current sections-as-ents works now. No need to introduce this new concept if the behavior is the same as it has always been.

finite magnet
# warped jolt It sounds like you want `Button` to 'auto-detect' when its children are text spa...

Actually, now that I have had a chance to think about it, I'm leaning towards the inverse. In games, paragraph-style inline/flow layout is uncommon - the vast majority of text usages are single words or short strings - button labels, character names, and so on. So I don't care if it's extra work on the user to have to prepare text strings for a paragraph (as long as it's possible), so long as the most common case has the best developer experience.

This means that the automatic conversion feature of the templating language should default to creating a self-sufficient TextNode. Thus, strings are automatically converted to TextNodes when used as template children. For cases where you want Text but not a TextNode, the user can do the extra work of wrapping the string in the appropriate component type - it won't auto-convert it once it is wrapped.

finite magnet
#

Each textual child of a flexbox would have to have its own separate layout context. The flexbox simply treats them as blocks.

warped jolt
finite magnet
warped jolt
#

It's like a mountain of allocations every time you insert or modify text.

tame aspen
#

I'm very happy to get fixes on the taffy side for that stuff fyi

#

(and I mean on the Bevy side too of course)

finite magnet
warped jolt
warped jolt
warped jolt
# finite magnet Looking at this again, I think that this example is not quite right. A flexbox w...

Based on my reading of the taffy integration, text nodes are treated as 'taffy nodes' without any distinction from non-text nodes. You specify a Style and an optional NodeMeasure for all nodes that need to be laid out. From the layout algo's point of view, text is an opaque box as part of a taffy node that can be dynamically measured and that's it. Once layout is done, the final calculated size of the node contents is stored in Node::unrounded_size on the bevy side, which is used by the text system to finalize text layout (see queue_text()).

From what I can tell, it would not make sense for multiple text buffers to be referenced as the content of a taffy node. So we only need one buffer on leaf nodes, which is what I proposed above. Conceptually it would seem that 'text content' should be a solitary sub-entity of a leaf taffy node, but in practice we can have any number of children so it's better to merge the text components into the leaf node.

finite magnet
#

That is, Taffy would only be used for Flexbox and Grid (not sure about Block). Display::None is also not controlled by Taffy for obvious reasons.

warped jolt
finite magnet
#

The 'display' property controls what layout algorithm is used for the entity's children.

#

For display Flex/Grid, Taffy is used to determine the position and size of each child.

#

For a paragraph block (however that is specified), Taffy isn't used to place the children; instead the children are processed either as wrapped text strings or inline blocks and glyphs are placed according to a line-wrapping algorithm, provided (I assume) by cosmic.

#

These inline elements can either be text or non-text. If they are non-text, they can be an image, a box, or any other kind of ui node - nothing prevents you from having a flexbox or a grid be inserted in mid-paragraph, it's just a box from the standpoint of the line-wrapping algorithm.

#

My reason for making this a display type was to make it explicit that this behavior is exclusive from other display types: you can't have a paragraph that is also a flexbox, that makes no sense.

#

However, I understand if there are implementation reasons preventing doing it that way.

warped jolt
#

cosmic text is only able to handle text and there isn’t anything on their roadmap about a more advanced API. I think you’d need some kind of integration between taffy and cosmic text to insert flex boxes/etc in-line to text. If you have a Flex node with one or more Display children, the flex node needs to measure those children in order to size itself properly. If the children then contain other embedded taffy nodes, then taffy needs to incorporate the Display calculations into its layout algorithm instead of using a black-box measurement approach.

I would say it’s not a good idea to add the Display option if we can’t implement it properly. Better to get an incremental text-oriented solution in place and then wait for all the pieces to come together before trying to add Display.

finite magnet
#

In fact the whole reason why the lorem-ipsum working group got unblocked was because of this.

#

That is, the comic-text migration PR was blocked for a year specifically because of the lack of support for inline images; and it only became unblocked when it was determined that this problem could be solved.

#

Actually, I'm wrong about that last point, I just checked the archives. The issue that blocked the migration was multiple font sizes, not inline images.

warped jolt
# finite magnet The previous discussion was about treating the inline block as a single large gl...

Ok, treating as a big glyph. Setting aside the details, let’s say there is a tree of things that coerce to glyph sequences. We want these to be concatenated and for cosmic-text to control their layout.

My proposal from above can solve this. When assembling a buffer in a TextNode, just look for everything in a contiguous hierarchy that coerces to glyphs. Let the Node entity that the TextNode sits on be essentially the ‘parent’ of the collected tree. It decides where the collected tree goes in the UI (via flexbox/grid) and coordinates sizing with the collected tree. If there is ambiguity when calculating layout between a TextNode and a child that looks like a Node but is actually being coerced to a glyph (eg an inline flexbox), we can use a marker component on the TextNode entity to block taffy layout traversal. If we want a coercable node to not be coerced, it can be marked with a component. TextNode = automatic Display behavior. The only difference is the TextNode is embedded as the content of a normal flex/grid node.

#

In the future if Style gets decomposed, a variant can be introduced that’s tailored to text (remove settings that apply flexbox content controls, etc) and TextNode can be updated to require that.

finite magnet
#

BTW, this is easier to reason about in the browser, because text nodes can't have styles at all - you can only inherit styles from parents. That is, in the HTML DOM a "text node" is a very simple kind of node with only a few properties, while an "Element" is a more complex node that can have attributes.

crude tinsel
#

I think Display::Flow vs a marker component for text roots is mostly an API question. I don't think there are technical limitations either way.

We will need a component to store the cosmic text buffer on either way, but whether that's what triggers (cosmic-)text layout, or it's a value of Display that does doesn't matter that much from an implementation pov.

#

Cosmic text doesn't support inline boxes atm, but it isn't blocked. We have it implemented in parley, and cosmic-text is architecturally pretty much identical.

#

I think we ought to move to a conceptual model where bevy owns the layout computations and delegates to Taffy or Cosmic-Text on a node-by-node basis. Taffy supports this mode of operation already (and special support from Cosmic-Text is not required)

warped jolt
tame aspen
#

Thanks a ton for writing this up!

warped jolt
tame aspen
#

My initial impressions are strongly positive, but I'll do a full review tomorrow

finite magnet
warped jolt
finite magnet
#

I'm still not fond of the idea of text nodes (that is, nodes that have a text string component) with children, such as your example 2.a - in the browser, a "text node" is a fairly simple struct that can't have children. In the case of Bevy, we can't prevent entities from having children, but how to interpret this is somewhat ambiguous - the fact that the children come after the parent is arbitrary. However, since the template engine will not generate text nodes that have this structure, it doesn't matter to me - but I'm hoping to make the implementation easier for you by dropping this requirement.

#

Now, one might ask: to what extent should browser behavior constrain our designs (if a hundred browsers run off a cliff, should you do so too?) I kind of assume that, except for features that have specific value for games, bevy_ui is a strict subset - that is, we imitate browsers for reasons of familiarity, but we're not attempting to get to feature parity. Any place where we support features beyond what browsers can do should be by intent and not by accident.

warped jolt
finite magnet
#

That is, in practice I think you either have unitary text nodes, in which the text string and the TextNode are all combined in a single entity; or you have an empty parent with text children. I don't think you will see cases where both the parent and the children have strings.

#

BTW I am glad you added the note about picking.

warped jolt
finite magnet
#

This is essential for something like hyperlinks.

warped jolt
finite magnet
warped jolt
#

When expanding markup text you’ll have a lot of empty Text entities for each span header for spans that don’t start with text, but those get elided in the layout algorithm. And an empty String (and TextStyle) ends up just a waste of some static bytes.

warped jolt
#

We could add another marker component TextSpan required by Text that can be used for text tree traversal. Then for markup text you can just insert TextSpan on empty nodes. I will add this to the proposal.

tame aspen
#

And we should make it clear when we're doing so, ideally with different naming

shadow marsh
#

Might be nice to add a section to the discussion explicitly addressing why it is veering from cart's proposal.

warped jolt
# shadow marsh Might be nice to add a section to the discussion explicitly addressing why it is...

The one where you just use Text and maybe Display::Flow? Basically to use required components you need a separate component to set up the root of a text block (hence TextNode and Text2d). ‘Auto-detect’ has a few problems: all uses of Text need to be aware of each other for proper auto-setup, the behavior you want is required components so it’s weird to have special magic for Text, you don’t always want Text on all the nodes of a text block.

I will update the discussion with this comment later.

shadow marsh
#

Yeah, I agree / have a vague idea from the headscratching I've done previously but it seems like you've given it a bit more thought and it would be great to get that in writing. Thanks

warped jolt
finite magnet
warped jolt