#Lorem Ipsum: A New Text ~~Backend~~ Frontend for Bevy
1 messages · Page 3 of 1
Added a comment to Implementation details about using custom-managed cosmic buffers.
@warped jolt I'm assuming that the TextStyle component will evolve over time; as I'm sure you know we've talked about making it easier to choose fonts (font family, system fonts, etc.) but that should be addressed in a different PR.
Sure, follow-ups are good. I'm actually doubtful bevy can/should have a font management framework. Or at least it should be opt-out/opt-in.
Considering I built my own framework. It's very opinionated and I don't see a way around that.
Well, there are several ergonomic problems with the current system that should be addressed:
- For complex text structures (for example, parsing markup into text nodes), having to assign a font handle to each section is onerous.
- Because there's no consistency in font asset names ("_Bold", "+Bold", "-Bold", etc.), specifying font styles is an error prone process.
- A lot of people have asked for the ability to use system fonts, but given that system fonts are different on different platforms, some sort of fallback mechanism is required.
Yeah what I'm saying is there's more than one way to skin this cat (feature set), so people should use ecosystem tools.
The fact there are few ecosystem tools doing this speaks to how challenging it is. And also indicates there's not much to reference for an in-house implementation.
In Quill, I have a subsystem that does text style inheritance: https://github.com/viridia/quill/blob/main/crates/bevy_mod_stylebuilder/src/text_styles.rs#L12 - basically what you do is add a marker component UseInheritedTextStyles to your text entities, and there's a system which walks up the hierarchy and searches for text style components.
My perspective is the opposite, I dislike inheritance and prefer everything explicit. In bevy_cobweb_ui, I added font requests where you directly request font handles from a FontMap (e.g. fonts.get(FontRequest::new("Fira Sans").bold())).
And there's a bunch of additional background code for auto-localizing fonts.
Well, in my case, building an editor - the entire editor uses the same font everywhere. So I prefer to specify the font one time. Now, the opinionated widget collection of course hard-codes the font selection in every widget, but not every ui element in the editor is an opinionated library widget - some are just app-specific widgets. Forcing the app to specify the font for every app-specific widget significantly increases the burden of building an editor ui.
Also, when people say that they don't like inheritance (and you are not the only person who has said this), I have to ask, is it specifically inheritance that you don't like, or is it the cascade/prioritization rules of CSS? Because basic inheritance is a lot simpler and easier to reason about than the CSS cascade, the latter which is known to confuse many web developers.
Every time I try to use inheritance in any context, I end up thinking it sucks and find another way. Over the years I have become quite functional-programming-oriented.
The problem with inheritance in general is non-locality. Last year you might recall I implemented a naive style-inheritance in bevy_kot. Ended up having bugs and generally not enjoying trying to reason about what the style state was.
My objection is strongest against implicit inheritance. If you do something like add an InheritFontFamily component to an entity then behind the scenes do either a direct or deferred font lookup, that's a design I can get behind because it's explicit and a local action (local to the entity spawn).
Well, I'm happy to bikeshed on the details. But from a widget design perspective, it is super nice to be able to define a standard "button" font style which is inherited by the button's children. This means that when my app uses a button and passes in a label, the label gets the right style without me having to explicitly set it.
In Quill, only text nodes inherit styles (with the marker component, but the templating engine inserts the marker automatically when it does String.into()), and the only styles that are inheritable are the five text style properties - family, color, and the various style flags. If you set a style on the text node explicitly, there's no inheritance. So you can inherit family but not italic, or vice versa.
I don’t want to get sucked into the design of font management. In any case, as long as the base API remains a handle I won’t complain.
@mental tree @finite magnet @shadow marsh @crude tinsel @near moon @vague cave Can you guys review https://github.com/bevyengine/bevy/discussions/15014 ? I will work on an implementation once everyone signs off on the proposed design. Other reviewers are welcome, I made the list from those heavily involved in UI and/or text.
Haven't been following as closely as I would like, sorry. The fonts.get doesn't imply something new being created here right, just signaling that "if you wanna do fancy font lookup stuff, it's possible but you're on your own for now"?
Anyhow, this satisfies an issue I had with the previous sections-as-ents impl which was how awkward it was to get back to a section entity from a section index.
And I am in favor of this limited scope as a starting point.
I am quite confused by
In this proposal
CosmicBufferis embedded inTextBlockand not accessible by users.
Er, why? I need the Buffer. Not sure how you would build a text input without it.
I was waiting to see what other comments people would add, but I have no objections at this time.
Yeah the fonts.get() is a demo from bevy_cobweb_ui, it just returns a font handle.
Don't you just need TextLayoutInfo and the raw Text entity for text input? The reason to keep CosmicBuffer private is when you use TextBlock the buffer gets automatically updated so it doesn't make sense for users to mess with it (we can expose an immutable getter though).
See tigregalis' prototype here: https://github.com/tigregalis/bevy_text_editor
and robtfm's impl on top of bevy_simple_text_input here: https://github.com/rparrett/bevy_simple_text_input/pull/74
cosmic provides some handy stuff for managing selections, navigating text by char/word/line, etc.
Ok hmm from what I can tell it only needs mutability for internal caching purposes. Will think about this.
I guess we can expose a mutable reference with the caveat that it should only be used for cursor-related activity.
I'd also point out that simple_text_input PR is cloning the buffer.
yeah, we have to due to limitations in cosmic right now but it's not ideal / I would hope that those limitations would go away at some point.
but either of those two users would have better information than me about this. I've been swamped lately and haven't been able to follow along.
I expect you could sprinkle some unsafe transmutation to store a reference instead of cloning. In any case, I'll include a mutable getter since a true workaround (where cosmic-text has an immutable path for the cursor API that just errors-out if internal caches are not set) may not arrive any time soon.
Cool, sorry my feedback is sort of limited to skimming and remarking on anything that immediately jumps out. But 👍 from me if that's resolved for what it's worth
Updated the proposal
Sounds good, every bit of feedback helps polish :)
Just about done with my 14.2 prs, I'll take a look tomorrow morning first thing
For hit detection, drawing cursors and drawing selections, I think we have to use cosmic-text's primitives as it has all the details on how each glyph is laid out visually.
For editing though, cosmic-text's editing feature is "lossy" and I hit a dead-end* with it; it's not possible to "round trip" and preserve all the entities. For example, in Bevy we might have two spans like ("Your name: ".bold(), ""). Cosmic-text will eliminate the second when it is used to display.
So I would unfortunately recommend recreating the editing experience from scratch, and using cosmic-text purely for text rendering. At least in its current state.
When there's an empty line (i.e. "\n"), the BufferLine loses Attrs information from the source. This results in a couple issues: When you clear the text in a buffer line, it falls bac...
I think that preserving the entities only matters if you are trying to build a rich text editor. What I'm interested in (and I think this aligns with Bevy users in general) are text editors for the following use cases:
- Naming objects in the editor (resources, tree nodes, labels, etc).
- Editing character dialogues, quest descriptions
- Naming objects in the game (saved games, characters, etc.)
None of these use cases require anything other than editing a simple string - even character dialog, which might have italics or hyperlinks, are more likely to use explicit markup rather than some kind of WYSIWYG editor.
Going back to the web as an example, few websites offer WYSIWYG editing because it's a hard technical problem. Most get along perfectly well with <input> and <textarea> which is the goal I think we should aim for.
@shadow marsh I take this back, the cosmic-text editor is changing the contents of the buffer. So I don't think you'd want to use TextBlock.
WYSIWYG editing in the web is hard primarily because of how the web is architected.
This feels like an artificial limitation though. I want to build both applications and games with Bevy.
We can render complex text layouts. We can apply transformations to it and control how they are applied. The rest are implementation details.
But I guess your view is that the author of a rich text editor would need to maintain their own data structures for interacting with a rich text document. It still feels limiting to me though. I think you can achieve it with the ECS as it is, and it's actually a pretty nice API.
Before we even begin to address this question, there's a more fundamental one: will the future Bevy text input widget use the same cosmic-text integration (TextBlock, etc.), or will it use some completely separate cosmic text integration (some new kind of TextEdit component)? This is important to decide because it changes the requirements - that is, when designing TextBlock does it need to handle all the requirements of editing, or just display?
I mean, if it's easy to combine them, implementation-wise, then sure.
The priority for TextBlock is code and asset driven text setup and control. If adjustments can be made to support editing, that's great but not needed for MVP as long as TextBlock doesn't get in the way of/opinionate the underlying CosmicBuffer -> TextLayoutInfo -> render text pipeline.
Something doesn't quite feel right to me about the new proposal. It feels more complex to me rather than less.
I see more things gradually becoming entities, and with that future I can see more things being "constructed as" hierarchies of entities; I think we should be addressing the ergonomics of constructing entire entity hierarchies. This is not a Text-specific problem, it's just that Text is the first feature to encounter it in any meaningful way.
I originally assumed that I would have to write my own text editing widget, built out of text primitives and key event handlers, because I figured any potential native Bevy text input component wouldn't be reactive in the way I needed it to be. But the other side of this argument is that HTML input fields aren't reactive either - React wraps them and listens to the appropriate events to make them reactive.
For example, consider a date picker widget. You want the user to spawn one thing with a bunch of config options, but under the hood it's a very complex arrangement of buttons and text and other layout.
The reason I'm putting any effort on this in the first place is to integrate markup with bevy_cobweb_ui. The current proposal should be enough (in combination with a text picking backend) to get good markup that includes hyperlink sections.
The way I address that issue is to make the syntax for invoking templates similar to the syntax for creating primitive entities and components. So in my framework the date picker would just be a template, and while syntactically it looks not much different from creating an entity, it's actually doing something more complicated.
I think that Bevy should provide a primitive for the "doing something more complicated". Just as Required Components spawn additional components for the user, something could spawn additional entities, whether those additional entities are part of the hierarchy or not, e.g. entity relations.
I believe that preon's PR would also be enough for you to do this though. Add a Hyperlink(Url) component to the TextSection entity.
The challenge is the ergonomics loss of spawning single isolated text blocks in native Bevy (which is in many users' views not sufficiently addressed by with_child). Since your library is in charge of constructing the UI hierarchy, the ergonomics of this is less important I'm guessing (it's an implementation detail).
The proposal addresses that but I feel it complicates things: there are now so many ways to spawn Text, and you now need to teach each of these and how they might interact with each other. And that's not considering how it may complicate the implementation.
There was a previous version of preon's PR that allowed the Text entity to hold the TextSection (with any children simply coming afterwards), but that complicated things, and it was arguably less complicated than what's being proposed here.
preon's PR is a precursor to this proposal, and presented some problems that I wanted to solve:
- Not integrated with required components.
- Doesn't allow adding text to the root entity.
- Does not support hierarchies of text entities, which is necessary if you want to do hyperlinks that contain multiple spans (i.e. via event bubbling: picking selects glyph in
TextLayoutInfoand gets span index -> look up entity inTextBlock-> submit event to entity -> event bubbles to the root of the span where the listener sits).
The API surface is larger yes, but it's all quite simple and there is no implicit contextual behavior which would be necessary if you want to hide UI and 2d text behind the same API.
We discussed deep text hierarchies here prior to preon's PR and I think the conclusions were:
- Any hierarchies can be flattened into a flat list of sections (and would have to be for display purposes).
- Text/flow-display-elements hierarchies could be built as a separate layer on top of the underlying primitives.
- Templating layers / UI frameworks would handle this for the user anyway.
Ok, and what about properly handling interactions on nested spans?
I think that is easily doable but not nice. I agree that first-class support for hierarchies is better here.
Easily doable as in?
It depends on the approach you take.
- The text block's "semantic" hierarchy is defined within Bevy, and is separate to the visual (bevy_text) hierarchy.
You forward interactions from the visual hierarchy to the semantic hierarchy.
- The semantic hierarchy is purely a templating concern, so you flatten the hierarchy, duplicating some data (or references to data).
Each section separately forwards interactions to the same handlers.
Ok I’ll grant that it’s possible, although not ideal.
Hey, haven't been active here lately, sorry about that, I got a bit sick, but feeling better now.
I read through @warped jolt's proposal for TextBlock. I assume this means my original text PR is not getting merged?
Also, if everyone does end up agreeing on the TextBlock API, I will gladly re-write my PR or start a new one for this.
We need a cart-approved design.
Yeah we do
I had hoped that sections/spans as entities could be made uncontroversial by doing it with very minimal changes, but that resulted in an interface that's sort of unpalatable. Your feedback on koe's proposal "from the trenches" would surely be appreciated.
not entirely sure what you mean, do you want me to give feedback to the proposal?
If you have any. Your point of view as someone who has been working on implementing this would be valuable.
alright, I'll take a more detailed look in a sec
Honestly, I really like the proposal. the one potential issue I see is confusion with the different components, there is a bit many, and the names are similar. not sure what to do about it other than properly documenting though 🤔
If you want to do the implementation work that would be a big help :)
I assume we should wait for at least cart's approval though
Yup I'll try to get to this today
Replied here with thoughts: https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459
Thanks @mental tree, good iteration on the concept. Should we proceed from here with an implementation or wait for more reviews/people to review your adjustments?
I made a comment about moving Text into bevy_ecs (or somewhere), otherwise LGTM.
bevy_ui can re-export it.
I'm also 👍 on Nico's comment about optimizing for macros/DSLs -- my editor has dozens of individual text strings, and none of them are spawned using the low-level APIs.
let me know if/when I can get started
@near moon @crude tinsel any objections to cart's updated API? https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459
I don't think so. I'm still keen on UI and 2D entirely merging at some point if there are no blockers, and barring that I think it might be good to put "UI" in the type (UiText, UiNode, etc), because we get quite a lot of people getting confused and using the wrong one.
I'm super happy with y'all's work ❤️
looks good so far
I haven't tested multiline and mixed direction text yet
I’d say go ahead and start. There’s overall broad consensus. Sorry your previous PR now gets superseded, and thank you!
Mixed RTL and LTR text works properly.
Diacritics and compound diacritics work properly.
Ligatures work properly.
Fantastic!
@sonic island Can I ask you to help us put together a little showcase for the examples to test this? It's much better to have someone who actually reads an RTL language prepare it
I will once I get rid of these RA problems...
I'll be happy to!
I had an idea in mind anyways so it's a good time to do both 😄
@tame aspen apparently Text2dBundle doesn't handle Text::justify if you don't set text_2d_bounds and defaults to justifying left (regardless of text_anchor).
The top text is TextBundle and the center one is Text2dBundle
I'll take a look at it tonight and see if I can fix it.
Fantastic, this will be handy for the release notes too
When using TextBundle, the ContentSize of the node is automatically calculated based on the text, so there is enough room for justified text lines. With Text2dBundle there's no space by default so I believe what you're seeing is essentially 'line overflow' from the transform point. There could also be some bugs though ^.^
Yeah none of the problems with text2d are bugs exactly, it works as intended but it's not intuitive and the API needs a redesign
oh https://github.com/bevyengine/bevy/pull/14270 wasn't merged yet so there are bugs in main still
Please review!
alright, I can get started on this now. sorry about the delay
I assume cart's proposal is what everyone agreed upon?
Yep!
Yeah I agree, we should really change Node into UiNode and Text into UiText. Much more clear. And helps with autocomplete 🙂
its unfortunate for 2D and 3D, since you cant (with good reason) start a struct name with a number (2DText, 3DText) But these contexts are quite hard to mix, and it would help a lot in naming components and concepts in a way that makes it easy to filter.
Personally I use ThreeD, and TwoD when I have to deal with it, just because I have not found a better option.
Putting the Text3D works, but I really like my autocomplete during coding, and that assumes I know I wanted Text, and knew that Text existed.
When using widgets or wanting to explore, and hope that something exists, i like to type:
- the context (Ui, ThreeD, etc)
- get presented with the possibilities in that context
the other case:
- I type Text
- I get presented with the other contexts in which I can represent texts
which is kinda useless mostly, and only helpful if I already know about text
We are getting kind of close to the next release. It would be good to know what y’all consider “incomplete” so we can prioritize.
If we are going to do refactors or renames, is it important that they ship in 0.15?
Refactors and renames should be quite low priority. Carts API suggestion looks really good to me, and I think that should get highest priority. Because it improves functionality and ergonomics.
Renaming and refactor: should IMO be prioritized a bit after, because they help mostly with discoverability and ergonomics.
and at this stage, I would argue that functionality > discoverability 🙂
A native text-input widget, even if focus and everything is not there, would be something I would want, quite heavily.
But there are not even suggestions for textinput widgets, unless we chose one from the community. So that might get crammed to get into 0.15, but I think should be a goal for 0.16.
Many games needs them, at least for inventory/recipe searching
Seems like focus is a pre-req for text input. Need to know when you click away.
Just a basic text input example (like a full screen notepad) might be easier now?
bevy-ui-navigation looks interesting.
Sorry that’s kind of out-of-scope for this discussion though.
Yeah, focus is needed, but also for first text-input, could be very rudimentary
basically, have a bool for the text-input, isfocused (true/false)
Would not be complete, but at least a start
True. I’ll try to spin up progress on focus, that seems like a pretty obvious next step after picking.
I wish there was a good way to pin a list to a discussion...
Like the notepad thingy from slack...
Some lists from me:
Text High Priority Ordered:
-
Carts API for textblocks/spans ( into 0.15 )
-
MVP for Text Input (doesnt even have to support multispan, or multiline or anything fancy like that, just a way to add/remove characters) (0.15 or 0.16 for first MVP)
Text Unsorted list:
-
Abstraction for system fonts
-
Renaming UI specific stuff, to not be unclear as to what is 2D stuff and what is UI stuff
Unsorted Ui:
- Observer/event bubbling ( @finite magnet )
- Focus (Not text specific), since I can imagine someone wants to like navigate UI, and even 3d/2d elements in a nice way.
- Clipboard support for text
- Shadows for UI
- Widget systems ( recently used Sickle UI, the builder patterns is <3, and the traits implementation onto commands is also ❤️ for creating a widget by storing entity references on a top-level component.
- ONE opinionated UI system natively
- Split up style
Done related to Text:
- better text-backend to achieve above items
- Ui picking ( would probably help with simple text input)
I probably forgot a lot, since I am not that active in here, but those things are stuff I notice when working in bevy
Some comments on your list:
- For focus, the main thing we need is to define a version of keyboard events which supports bubbling. Everything else can (for now) be third-party library code - different third-party libraries can interoperate as long as they agree on what the event type is, they just need to stick observers in the right places.
- Not text related, but if I could get support for "fragment nodes", this would unblock a lot of stuff for me - I'd rewrite both quill and bevy_reactor, and both would get smaller. Similarly, I really think that if BSN is going to support any kind of reactivity you are going to need this in place first.
Also, with respect to focus, there's a whole conversation around "keyboard focus" vs "mouse focus", and how these should interact. Obviously, if you have a text input widget with a cursor, keystrokes need to go their first, regardless of where the mouse is on-screen - if you hit control-X/C/V/Z you want it to affect the text and not be interpreted as a global shortcut. But the text input widget might not be just a single entity (lots of text widgets have decorative frames, adornments like clear/search buttons, etc) and the entity listening for events might be one or two levels higher than the node that has focus, so bubbling is going to happen. But if people that want Blender-like shortcuts where the set of shortcuts changes based on where the mouse is, that kind of conflicts with the keyboard focus model. I'm not sure what the answer is on that one.
If we get a way to indicate focus onto a specific entity inside bevy, users could implement their own focus logic
and bubbling ofcourse.
then I guess maybe some focus experimentation could start to answer many questions
and raise new ones
I have been using bevy_a11y::Focus.
It's a resource which contains an Option<Entity>.
that used with bubbling, wouldnt that unblock a lot of experimentation?
Yes
But do you think that text-input could be done without that, and just with the a11y focus?
(wondering if I need to change my prioritized list)
Here's what I do:
- When I get a keyboard event from the event queue, if focus is not none, I send a bubbled keyboard event to the focus entity.
- If focus is none, I query for an entity with DefaultKeyboardHandler and send it there.
- Text input widgets receive bubbled events and call stop_propagation if they recognize the key.
- Buttons and checkboxes also do this for ENTER/SPACE.
- I have a reactive context method
cx.is_focused(entity)which returns a boolean which is true if the widget is focused. This is used to draw the focus outline. It reacts when focus changes. - Global shortcuts are just listeners which are placed on the app's root element, which also has the
DefaultKeyboardHandlercomponent. - Tab navigation is handled by a special system.
All this lives here: https://github.com/viridia/quill/blob/main/crates/bevy_quill_obsidian/src/focus.rs
do we even need bubbling? can't widgets read their focus state and determine if they should grab input or not?
The reason we want bubbling is that widgets sometimes want to pass through keyboard events they don't handle
If we are cool with using observers for that then I can write that in a weekend. (maybe)
For example if a button has focus, you can still press ctrl-Z to undo.
A slightly different approach is one where, instead of using the focus resource, we send keyboard events to whichever entity is being hovered by the mouse and let the events bubble upwards from there. This gives us the Blender-style contextual shortcuts - that is, where the shortcuts change based on which panel the mouse is in. You basically just stick an observer on each panel that knows about the shortcuts it knows.
However, if you want both - that is, keyboard events dispatches to both the focus entity and the hover entity - then you are probably going to need to dispatch the key event twice, which now that I think about it is probably not the worst idea in the world. The global handler would collect any events that were left over when the widgets were finished with them, and dispatch them to the current active panel.
oh! also, now that we have input event ordering, we can actually interleave these keyboard events with point events in the correct order.
that seems useful.
it might matter if you press a button before or after a drag drop event, for instance.
I honestly don't think it matters, the hardware has different clocks
ah right, cool. we don't have to do it that way.
but on some inputs, button presses and joystick moves will have the same clock. so idk.
controllers off topic anyway.
I think the main requirement for ordering is multiple input events which are produced by the same hardware event, like drag end and drop, which are both generated by mouse up.
what happens if two input devices both take actions that would change focus?
last one wins?
I mean, honestly, I don't think I care which one it is, so long as the result is "reasonable".
I definitely think we'll want bubbling. But we could probably get basic text input without if need be?
Not text related, but if I could get support for "fragment nodes", this would unblock a lot of stuff for me - I'd rewrite both quill and bevy_reactor, and both would get smaller. Similarly, I really think that if BSN is going
You seem particularly keen on this feature: have you considered implementing it yourself? No obligation of course, but I don't think this feature should be particularly hard to implement (and the bevy_ui codebase is quite small and approachable :))
I started looking at fragment nodes/shadow hierarchies a bit, draft at https://github.com/bevyengine/bevy/pull/15238
This would be my first contribution so any feedback would be greatly appreciated!
I did actually spend some time looking into what it would take. Part of the problem is that the idea, as proposed, would have broad impact - I originally assumed that it would only affect layout and rendering extraction, but other design proposals would have an impact on observers and queries as well. I don't have a good sense of "how many places in Bevy do we traverse entity hierarchies?", which is something that requires broad knowledge of the Bevy code base. Up to this point, I've only worked on a few small pieces of Bevy (AssetPath and bevy_color)
What I am asking for, right now, is not so much an implementation as a general sense of whether other people think this is a good idea or not.
How would it affect observers/event propagation though? IMO it makes sense that those should include shadow nodes as well, so they could just use Parent/Children with no concern to shadow hierarchies, right?
Transform and visibility propagation might get a bit tricky though
I think that observers should treat shadow nodes no differently from other nodes: that is, if I trigger an event on a child of a shadow node, a hook placed on the shadow node should be able to receive the event.
Yeah exactly! We are on the same page then
Some questions I would like to ask are:
- Is this idea even feasible?
- Is it hard?
- What parts of Bevy would be affected?
- Is anyone else interested in this idea?
- Are there other use cases for this besides the ones I have thought of?
BTW I do like the name "shadow node".
I for one am interested in the idea at least (web dev background). And I feel like there should be other use cases than for reactive scenes/UI, but can't think of anything right now...
As for the difficulty, I will continue exploring it and hopefully find out 😅
Actually, we really should be discussing this in ui-dev
Right, forgot which channel we are in. Gotta head to bed now though. But I'll continue exploring this in the coming days
thread 'Compute Task Pool (21)' panicked at c:\dev\tools\cargo\registry\src\index.crates.io-6f17d22bba15001f\cosmic-text-0.12.1\src\buffer.rs:651:13:
assertion `left != right` failed: font size cannot be 0
left: 0.0
right: 0.0
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `bevy_ui::widget::text::measure_text_system`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
This can be hard to track down. Theres no indication of what node is causing the problem.
We should be able to filter out segments higher up to avoid that.
In the latest Bevy main, vertically centered text looks like it's a pixel too low (to my eye anyway). This was true under the old text code as well, and I kind of hoped that moving to cosmic text would fix it. I couldn't find an open ticket for this, but I remember there was discussion about this at some point.
Note that for some sizes it seems OK, while for others (the "Xs" case) it's fairly obvious.
I'll PR a fix for this today.
Looks like bad rounding?
Sounds like UI rounding is all-around a mess and needs a deep correctness pass.
Please review https://github.com/bevyengine/bevy/pull/15368 ❤️
Yeah, fully agree
Is this using latest main?
From yesterday
Cool, I can test this on my crash later today.
Not sure if this is what is causing these problems, but I believe UI rounding ought to happen in physical coordinates at such a point that it is never translated back into logical coordinates. But that this is not currently the case.
This fixes the crash. I don't know what has a 0 sized font, but it should be easier to track down when the panel isn't panicking, thank you.
@true zephyr looks like release candidate for v0.15 is tentatively Oct 4th. Do you think the text PR you're working on will be ready for review by then?
any concerns/objections to this? https://github.com/bevyengine/bevy/pull/15479
No objections from me. It's a pretty standard "expose more of our internals for advanced users" decision
@velvet estuary this is the proposed text solution https://github.com/bevyengine/bevy/discussions/15014 and @true zephyr has indicated he will implement it, although that may have changed.
Ok, nice. I was just trying to label all the milestone issues with an updated status, but let me know if you have any small fish you'd like me to help with :)
@tame aspen @shadow marsh checked out the bevy_simple_text_input. I would say this is something we could upstream into bevy no?
Or would there be something blocking that?
Yeah, that sort of thing is very much something I'd like to see upstreamed
I'm not sure on the implementation details, but I think that we're ready for a PR now
Honestly, its 900 lines, contained in a single file. has no external dependencies other than bevy and is well documented I would say.
quite feature-complete for a simple single-line textinput as well
Upstreaming is not a goal for me personally. I just wanted to see if I could do something good enough for a lot of people within the current limitations of Bevy, and outside of Bevy's bureaucracy. The whole thing is kinda janky really. Less janky all the time thanks to character input improvements / upcoming cosmic text stuff though.
But the roadblock preventing widgets like this being included in Bevy hasn't been solved. That's (IMO) a lack of consensus on how to implement widgets that require hierarchies, and what should be done about "reactivity." The current disposition seems to be to wait for bsn or whatever. I don't personally understand why an implementation detail like that needs to block progress on widgets. 🤷♂️
Attempts to add simpler widgets than b_s_t_i have been deemed controvertial and/or bikeshed into oblivion. So I don't really see it ever happening.
IMO some of the simpler widgets are things that don't necessarily need to built-in to Bevy, and could be implemented as high-level components if such a system existed.
Text input is different though and probably does need to be built-in
@shadow marsh While I agree with your points regarding widgeting and such. I believe that with @tame aspen blessing on this now, upstreaming the text-input to have something to start with would be very beneficial.
I believe that by adding ONE way to create hierarchical widgets to bevy, will open up a lot of possibility. And your implementation is the simplest one out there, and will get bevy beginners started quickly.
I also believe that to actually get any kind of good bsn, it should be driven by real need, such as a simple widget, for example a text-input.
With that said, I could do the "work" of copy-pasting your code with your blessing and co-author you in it and try to push the PR through.
Yeah, I'm interested in getting something simple and not-terrible in place for this at the start of the 0.16 cycle @twilit iron, and then refining it as BSN itself drops
No chance of pushing it into 0.15? 🙂
No chance, sorry
I'm hopeful that we get the Text rework in, but even that's a big stretch
Seems unlikely unless someone jumps on that work. I don’t have time to do it.
Yeah. I'd do it myself but the Interaction stuff from #1236111180624297984 is more pressing
Welp, next cycle it is
I may be able to shoulder the interaction depreciation
It would be nice to get text in, and if it’s a choice between which you work on, me doing it will just mean I have less time for picking examples.
Isn't it also needed for the required components migration? At least with the blessed/selected proposal
(text I mean)
Yes but I also don't want to do the Text migration 😂
I thought you’d say that, lol. Fair enough, it can wait.
I don’t want to do it either
Doing a huge grindy migration and then the release notes was brutal last time lol
I am hoping to be in a better place to help with release notes this cycle. Didn’t have spoons last time.
I am very sorry about not working on this yet. I was busy with life and honestly my mental health isn't that great lately. Not sure when 0.15 is planned, but if noone else took over, I want to try to work on this again.
That’s totally cool, happens to me all the time. Don’t beat yourself up about it, prioritizing your own wellbeing is exactly what you should be doing.
You’ve been super communicative about the status and that’s really all that matters ❤️
We are going to start the RC process for 0.15 on the fourth, just so you’re aware, but please don’t crunch to get anything in by then.
Yeah I will work on it but I don't think it will be done by then. honestly might be better for both my stress levels and the time we need to get proper reviews
And thanks btw, I really appreciate it!
Yeah, don't worry about rushing it 🙂 We can get this in for 0.16, no problem
I’m considering implementing it today to try and squeeze it in to v0.15. Would you be able to review it this week? Or do you strongly want to implement it yourself and think we should hold off?
Go ahead! If we can get it into 0.15, that would be awesome!
Ok cool, I'll do that then
@runic belfry I haven't been able to pay attention to Bevy in the last couple weeks. Is there anything you've found in your cosmic text / bevy_simple_text_input experimentation that we might want to address in Bevy and/or cosmic before 0.15?
Would be nice to get a new release from cosmic-text for some perf improvements.
But the timeline probably isn't feasible.
I’ve not needed to change anything internal. I am on 0.14+the cosmic pr though, not tracking main
I'm more of an outsider here, but I'd also happily give it a review since it's related to the required components migrations
Please review this, it's blocking for the text rework: https://github.com/bevyengine/bevy/pull/15581
And merging
Posted a draft PR. It isn't close to compiling yet, but has all the main changes now. Tomorrow will be getting it to compile then fixing all the tests and examples, etc. Also haven't rebased onto ghost nodes yet. https://github.com/bevyengine/bevy/pull/15591
Is this ready for a first look?
Like, start of a review or not yet?
You could skim but probably not worth a close look yet.
I'm anticipating some trouble with lifetimes for this iterator TextSpanIter if you have any suggestions: https://github.com/bevyengine/bevy/blob/80c051bbedf62598d32d78f195361fbb36affd35/crates/bevy_text/src/text_span.rs
The text rework PR is ready for review https://github.com/bevyengine/bevy/pull/15591. I have migrated 1/4 of the examples (or 1/4 of the compile errors anyway). Looks like there is only 1 day remaining before RC, so it urgently needs review to make it in @true zephyr @finite magnet @near moon. @mental tree you may want to review it, the changes are pretty massive (affecting ~50% of examples) and the migration is a little tougher than expected (had to introduce TextReader/TextWriter system params for accessing spans).
Awesome! I will take a look somewhere today (9am for me right now)
I have some ideas for demos I would like to do: show a line of text with a single word in a different color, where the color animation is driven by an Hsla hue rotation using a component attached to the text node containing a timer.
You're welcome to PR against my branch with new examples.
Also, does anyone remember the old-time TV commercials where you had some musical jingle with lyrics printed at the bottom of the screen and a bouncing-ball hitting the words in time with the music? Something along those lines 🙂
Sorry I missed the discussion here. I will write a BiDi demo. What do you want to demonstrate?
Currently I am just using a flex and grid layout to display mixed direction TextBundles.
rough draft:
Someone mentioned multiple text sections so I will add different text styles in the same text.
ping me with your requirements 🙂
Is this ready to merge after fixing the examples or is it still undecided?
i.e. should I base my bidi example on it or is it too early?
I think alice is keen to merge it asap
I'm working my way through it and most of the things I have problems with so far are fairly superficial if changed
I'm just updating the text stress tests for my review, do you want me to upstream them to your PR if you haven't done them by now already?
performance seems ~5% improved for text relayout but seeing some dips in fps, slightly worse when text is unchanged but nothing significant
Left a 👍 on some comments I agree with, and left some comments myself. other than that I think the PR looks great!
There was a bug in your changes which probably caused the improvement accidentally.
Hopefully getting this merged late today or early tomorrow.
text_pipeline showed the biggest improvement so I don't think so.
maybe I was imagining it though, I'll run it again
Yeah ran it again, and still seeing a significant improvement
Looks like the main combinations are FlexStart/FlexEnd, JustifyText::Left/JustifyText::Right, and then the implicit LTR/RTL information embedded in a text. Then there is vertical and reverse-vertical(?) text but IDK if we support that (maybe FlexDirection?).
Column and ReverseColumn will not work without CSS direction property
E.g. where you horizontally align items depends on whether it’s rtl
I plan on contributing a patch to taffy but it will take a while (certainly not before RC)
Thanks for the review! Many of your comments are changing @mental tree's 'blessed design' so it's hard for me to address them without maintainer direction @tame aspen.
I will try to make a better constructor for spans at least.
I didn't mean to be too negative about it, just there are lots of changes there to comment on. I think it's a good PR overall.
I'll take a look
Ok addressed all of @near moon's comments aside from the design feedback. Will get back to updating examples later today.
FYI, looking to push back the release candidate a week: #engine-dev message
Which means we should definitely be able to land the Text rework
And maybe a system font solution?
This will be helpful for follow-up PRs, especially if some redesigning is needed.
I want to bring up the issue of not using ghost nodes for text spans, but instead adding smarts to the layout code not to dive into text blocks. While I am still very bullish on ghost nodes, I also understand the desire to put it behind a feature flag. I also think ghosts may be overkill for text nodes.
I'd rather have a generally-usable feature than special-casing for text spans.
I thought I mentioned this in my review as well but it seems like the comment got lost somehow. I don't understand the need for ghost nodes here, can't we just walk the tree normally from the root text node? I thought ghost nodes were for replacement of parents by their children, which isn't needed here.
Making them GhostNodes means they have to require Transform and Visibility components as well, so the propagation systems have to update all those nodes uselessly. And it's potentially confusing for users who don't realize that those components don't serve any purpose.
No, GhostNode is so the layout will skip them properly. It’s currently the only solution, I did not task myself with finding another solution.
It should just be enough to stop any walk at nodes without either a Node or a GhostNode component?
We could look at it in a follow up, but probably the most controversial part of the PR is the reliance on ghost nodes
You can check the discord links in this comment https://github.com/bevyengine/bevy/pull/15341#issuecomment-2392344534. Those are better/more universal solutions that came up shortly before the ghost PR was merged. But someone needs to write a design discussion and get consensus and implement it properly. Or we need a completely different approach that isn’t in-hierarchy.
You don't have to skip them necessarily, could make them full UI nodes but set them Display::None in layout. It's a bit clumsy but simple and doesn't use ghost nodes.
Ghost nodes were easy and they worked today 🙂
Then you need to lug around Node and Style components, bloating memory use. And you don't get away from Transform/Visibility because Node will surely require those when migrated to required components. GhostNodes are the least-bad solution in my view, and we can easily revisit if a truly better solution appears. They are basically an implementation detail with minimal exposure to users.
Just wrapped up my first Text Rework review pass:
https://github.com/bevyengine/bevy/pull/15591#pullrequestreview-2346885986
Thanks :) responded
All examples are now migrated. Will work on docs and migration guide tomorrow.
Apologies, no time to follow closely lately and do a proper review. But I took a quick peek and tried to catch up on some of the discussion, and
I don't totally understand this TextWriter business. It seems potentially useful, but with most examples having been migrated sort of mechanically to use it, we seem to be really underselling the benefits of spans-as-entities. IMO, most of the examples mutating spans should be querying for "marked text span entities" and modifying them that way. Similarly not really understanding spawn_text_block, which seems really inflexible.
And just another small note: at least a few of the examples using a large amount of text spans are doing so for no good reason, just because it looks nice to have the code formatted that way. The spans are totally static and could just be one big span with line breaks.
maybe the grouping of text nodes should just be implicit
with any adjacent text nodes in the tree getting grouped together automatically
and text nodes would always be leaf nodes, never have children
what if I want to have a background color follow a specific word, and that word still being placed according to the text flow in a paragraph?
At least when I talk about not doing text hierarchies, it's just "for now" because not having it isn't a regression, and it's not going to get done in time. Not saying it isn't useful.
The word would be a text entity that you could give a background colour component and then it would draw the background around the word using the glyph geometry from text layout info
but I want to draw the background with my own shader so that it's a trippy star
background color is maybe a bad example 🙂
It's less efficient maybe but we could split up text layout info
So each text span would have it's own layout info just with the glyphs for that span
So that would make it easier to pick out the geometry for a single word and draw it with a shader or whatever
The commonly done thing is to have all that information in the root text context. But keep track of spans by e.g. entity ID so that this is still possible.
The TextWriter API evolved as part of me trying to make the migration less painful. It's very practical. IMO if people have problems with the available helpers (TextWriter and spawn_text_block) then they should make follow-up PRs to iterate on what this PR does instead of trying to make this one gigantic PR perfect. Sorry if I sound grumpy, just woke up after spending 3 days and 23hrs of work on this.
@true zephyr's PR did this, using marker components and query single for mutation.
It looks very ECS-native
fits my preference but I understand if you don't want to tackle that (it was quite a lot of work for Preon)
Yeah I did do that. And honestly, if no one else wants to do it, I can probably port quite a few examples this weekend as I quite literally have nothing but programming planned.
I can probably re-use a bunch of code from my PR too
If you guys want me to do that, let me know. I would probably do it in a separate PR I assume? As in a follow-up, like @warped jolt said above.
My comment was prompted by a comment that seemed to be questioning whether spans-as-entities was worth the hassle. And that situation was resolved by cart if I followed the discussion properly.
I'm fine with leaving the example cleanup for later.
But I'm not sure if there are any examples where we actually should be using TextWriter so it probably should get done eventually. (And add an example showing off TextWriter if that last sentence turns out to be true)
I was thinking about text materials in particular. If extraction is done per entity, text materials could be supported where you just add a UiMaterial component to an individual text span entity. We'd keep the cosmic text buffer still in the text root entity, just split up the output glyph geometry per entity.
This would be ideal, otherwise the current PR will get dragged out too long. Need to merge asap to avoid constant merge conflict repair.
pretty serious bug: https://github.com/bevyengine/bevy/issues/15543
I've made the various adjustments discussed for the Text Rework and merged with current main. Should be ready to merge once the remaining CI issues are fixed:
https://github.com/bevyengine/bevy/pull/15591#pullrequestreview-2355836221
One thing I plan to do soonish is port bevy_reactor to the new text components; I was holding off on this until things had stabilized. The UiBuilder class has two helper methods that create simple textual entities:
fn text(&mut self, s: impl Into<String>) -> &mut Self;
fn text_computed<F: FnMut(&Rcx) -> String + Send + Sync + 'static>(
&mut self,
text_fn: F,
) -> &mut Self;
90% of use cases only involve a single text span, so the builder API provides methods that hide all of the messy details of text blocks and spans.
These methods are also implemented on WorldChildBuilder via an extension trait.
Note that simple APIs like this are only useful in a world where text styles such as font and color are inherited. Unstyled text generally looks out of place (because it uses the default built-in font), and extending the "simple" API to allow for styling makes the API considerably more complex. Instead, I chose to allow the styles to be specified on the parent, and have them propagate downward unless overridden.
For cases involving multiple spans, with span-specific styling, there are a couple of approaches:
- Let users do it themselves by spawning the entities manually.
- Leave it up to some markup parser (markdown, HTML, etc) to properly set up the spans.
- Design a more advanced API with more options.
Fixed CI lints, did not have time to fix system ambiguities.
I think we'll need to ask devs in all the working groups/channels to run the examples they are familiar with once this is merged. With diffs this big (and including all the other migrations) there are bound to be a number of bugs.
@tame aspen latest push to https://github.com/bevyengine/bevy/pull/15591 should give green CI, can you hold off on potential merge conflicts until it can be merged?
You bet, this is my priority 🙂
Two more system order ambiguities:
Green!
Text rework is merged! Thanks everyone :)
Very pleased to get that in 🙂
Test it out! Small improvements will be much less painful now that it's on main
Follow-up PR: https://github.com/bevyengine/bevy/pull/15797
Objective
Improve clarity when spawning a text block. See this discussion.
Solution
Rename TextBlock to TextLayout.
@mental tree I imagine you'll want a say in this one ^
Thank you so much! I was waiting so bad for this PR 😄
Really appreciate it
fn change_text_color(mut q_text: Query<(&mut TextStyle, &mut AnimateTextColor)>, time: Res<Time>) {
for (mut text_style, mut animate) in q_text.iter_mut() {
animate.hue = (animate.hue + time.delta_seconds() * 200.).rem_euclid(360.0);
text_style.color = Hsla::new(animate.hue, 1., 0.5, 1.).into();
}
}
Ooh
God that's so nice
Text as entities is such an improvement
Proper font abstractions is next on my wishlist (0.16 tho)
I've finished porting bevy_reactor to the new API and current Bevy main
How bad was the text migration?
Not bad at all, simplified some things, I have one nit.
The code for the demo above:
builder
.spawn((TextBlock::default(), Text("".to_string())))
.styles((typography::text_default, |sb: &mut StyleBuilder| {
sb.font_size(32).color(palettes::css::GRAY);
}))
.create_children(|builder| {
builder.spawn((
Text("The quick brown fox jumps over the ".to_string()),
TextStyle::default(),
UseInheritedTextStyles,
));
builder.spawn((
Text("lazy".to_string()),
TextStyle::default(),
UseInheritedTextStyles,
AnimateTextColor { hue: 0. },
));
builder.spawn((
Text(" dog".to_string()),
TextStyle::default(),
UseInheritedTextStyles,
));
});
Note how the top-level text block has an empty string
For this demo, I wanted to use "child spans only", so I had to insert a placeholder string at the top level.
Wouldn't render without it.
One thing I haven't tried, although I am curious about, is whether I can mutate Transform to get individual words to "shake".
Probably not I would expect.
I was thinking about the old-timey TV commercials where you have some musical jingle with the lyrics shown at the bottom of the screen, with a bouncing ball bouncing off each word in time with the music.
I suspect that's another case where it will clash with layout setting the transform. I believe we still need a ui-specific transform to avoid this.
or just schedule the transform shaker system after the layout
That might work, so long as you're not expecting to use Transform as input.
You can do Text::default()
This will do nothing, the spans are collected into the buffer, transform/visibility are completely ignored.
That's fine, if I really need to do something like that there are other ways to do it.
You might be able to hack into TextLayoutInfo to change glyph positions.
Can use span_index to identify the target span (with ComputedTextBlock::entities to get the entity list).
I was thinking I could wrap the word in a separate element, treat it as an inline block.
Actually, what I am more interested in, in terms of next steps, is accessing the cosmic-edit API
You wouldn't be able to wiggle 'over' other glyphs, I don't think.
What about picking? If the word is a hyperlink, can I put a picking observer on one word span?
Not integrated yet
Should not be hard to adapt existing text picking to it.
Help screens in games often have links, so this is a real use case and not a hypothetical like the shaking thing
Sure. I want it too. Hopefully someone implements it :)
So, next steps on the lorem ipsum agenda:
- font abstraction
- cosmic-edit
- picking
And loading system fonts
Animated text the simple thing is to write a custom extraction system, then you can just adjust the positions of the glyphs any way you want
or it used to be, maybe required components gets in the way
I guess you have to do something ugly like set the text color to transparent and then it'll still work
Note that TextBlock::default() is unnecessary to include manually, as Text requires TextBlock
Same with TextStyle ^
migrated b_s_t_i.
My only issue/feedback so far is that it's very easy to goof and add a Text as a child instead of a TextSpan, and when you do that,
- things just sort of look broken, no warning/error
- unless you try to modify a span with
UiTextWriter, which panics on an unwrap
Maybe we need to add some "this makes no sense" warnings.
In fact...
Here's something to think about: with required components, ghost nodes, the changes to node traversal, and text entities, we are starting to produce more cases where certain hierarchy patterns are invalid. I wonder if it's possible to generalize these in some ways. On the one hand, this is problematic because of crate dependencies, the kinds of validation rules you'd want use components from various sub-crates. On the other hand, they all have to deal with the problem of not running the validator too often or making it too expensive to run.
We could do debug mode systems that check 🤔
Kind of like a "runtime entity lint"
Mentioned this before, but I have a generalized runtime hierarchy validator in big space, with a declarative API. I really think it's worth upstreaming for runtime validation. It produces text output to describe why the lint is triggered, the archetype it was triggered on, and can support custom messages when you define the lint.
Output looks like:
-------------------------------------------
big_space hierarchy validation error report
-------------------------------------------
Entity 8v1 is a child of a "Non-root entity in a high-precision BigSpace hierarchy", but the components on this entity do not match any of the allowed archetypes for children of this parent.
Because it is a child of a "Non-root entity in a high-precision BigSpace hierarchy", the entity must be one of the following:
- Root of a low-precision Transform hierarchy, within a BigSpace
- Any non-spatial entity
However, the entity has the following components, which does not match any of the allowed archetypes listed above:
- bevy_transform::components::transform::Transform
- bevy_transform::components::global_transform::GlobalTransform
- bevy_render::view::visibility::Visibility
- bevy_render::view::visibility::InheritedVisibility
- bevy_render::view::visibility::ViewVisibility
- bevy_hierarchy::components::parent::Parent
- bevy_hierarchy::components::children::Children
Common errors include:
- Using mismatched GridPrecisions, like GridCell<i32> and GridCell<i64>
- Spawning an entity with a GridCell as a child of an entity without a ReferenceFrame.
If possible, use commands.spawn_big_space(), which prevents these errors, instead of manually assembling a hierarchy. See src/validation.rs for details.
It came about for similar reasons, I think. The shape of the hierarchy was semantically important, and silent failure wasn't a nice DX.
Let me know any bi-directional use cases you want to test.
I am planning on replacing the message with a long and text-wrapped message with multiple styles.
Mixed bidirectional text please!
On it.
And bidirectional text selection eventually
@tame aspen one thing to note is that the start of the text determines the direction automatically.
notice where the punctuation is placed
especially the last period
I added Arabic "ع" and English "E" right before the period to confirm that the punctuation placement direction is decided by the beginning of the sentence.
So far the arrangement looks sensible to me. I need to try it with text spans now.
Fantastic!
Here is the same with separate text spans.
(I switched the text justification in the middle)
Looks like we're getting some overflow, but I think that's not your fault
got a bit crazy with the colors lol
the text renders correctly despite changing font size in the middle
i love cosmic text ❤️
Looks like the overflow check doesn't check if the beginning of text is overflowing.
So if LTR text is overflowing to the left because the text is right justified then it goes out of the box..
Sounds like a cosmic_text issue is in order?
Maybe 🤔 I want to work on taffy a bit to add the direction property.
overflow issues are a bit more complex so I'll check it out after I get direction working hopefully soon™️
I don't have a pure cosmic_text test project to isolate the issue (if any).
Ugh, I was going to create a thread and forgot that I can't create threads in WG channels 😦
I wanted to start a thread about text editing support (for games) in Bevy
The core problem for editing in my opinion is translating pointer position to text cursor position. Everything else falls into place after that is solved.
Yeah I made a simple text editor for hedra using ab_glyph wasn't that difficult
to get multiline editing and selections blocks to work had to write my own glyph layouting to work around the bugs in ab_glyph, should be much simpler with cosmictext
I believe cosmic-text's editor abstraction has this built-in
calculating the cursor wasn't a problem anyway, you have the glyph geometry already in the layout
Awesome. Then all that's left is deciding how UI focus and navigation should be handled.
yeah navigation and focus yeah that was what made me sweat
"all that's left" == 90% of the work 😄
cross platform support was annoying as well to sort out
Note that the cosmic-text editor can’t be used directly with bevy’s ComputedTextBlock because the editor invasively modifies the cosmic-text buffer.
At least, that was the conclusion of my investigation.
Right, I have the docs note
/// This is private because buffer contents are always refreshed from ECS state when writing glyphs to
/// `TextLayoutInfo`. If you want to control the buffer contents manually or use the `cosmic-text`
/// editor, then you need to not use `TextLayout` and instead manually implement the conversion to
/// `TextLayoutInfo`.
All right, here are my notes on text editing:
First, there's an open ticket https://github.com/bevyengine/bevy/issues/6213 but it's 2 years old, and enough has changed that it may be out of date - but the basic requirements seem sound.
What I want is a text editing widget for games. Use cases are character name, email address, password, save file name, multi-user chat, and so on.
I realize that there's an ongoing effort to build a text input widget for the editor, but what I am envisioning is somewhat different.
Goals:
- Text color, font, cursor, and selection color should be customizable.
- For password entry, there should be an option to display only placeholder glyphs instead of actual text.
- It should interoperate with other keyboard focus widgets (see the discussion here: https://github.com/bevyengine/bevy/discussions/15374)
- Basic multi-line editing would be a "nice to have" for things like multi-user chat boxes.
- However, we can assume that the text being edited is short - no bigger than a tweet.
- It should allow third-party frameworks to extend the text input widget in various ways using observers, but these extensions should not be built-in. For example, a numeric spinbox can support editing numbers by dragging, or incrementing using arrow keys, but this should not be a feature of the core widget.
- Scrolling support
Non-goals: - Syntax highlighting
- Large documents
- Source code formatting
- Rich text styles
In other words, if you are editing long docs or source code, use an editor specialized for that purpose.
Are you familiar with https://docs.rs/cosmic-text/latest/cosmic_text/struct.Editor.html ?
A wrapper of [Buffer] for easy editing
I've looked at it briefly
In React, there are two kinds of text input widgets: "controlled" and "uncontrolled". I'd like to build a controlled widget - this is a widget which doesn't update its own state, but instead triggers an event with the proposed new state, such that the receiver (usually the parent widget) can decide whether or not to make it the new state or take some other action. So for example, when I hit the 'delete' key, the widget doesn't mutate the current text state directly, instead it triggers a "change" event with a copy of what the text buffer would look like with the character or selection range deleted. It's up to the parent to apply that change.
Controlled widgets are nice for things like field validation - for example, you can have a phone number input, and if the user types an invalid character you can flash the screen or display an error. They are also good for cases where the text input can change programmatically - such as the case where you have a hex color input that you can either type into, or can be changed by dragging the "hue" slider.
Uncontrolled widgets, on the other hand, are slightly more performant in editing, because keystrokes can be applied to the buffer directly. But they require additional synchronization in some cases because now there are two sources of truth for the text, and which source is authoritative changes depending on whether the input widget has focus. Also there's no way for the parent widget to cancel the effect of a key, so validation can only be done after the fact.
As you might expect, controlled widgets work best with small text buffers, since every change event contains a clone of the buffer with changes applied.
So what I am envisioning is something like a TextInput component that emits TextChange events which the observer can then handle.
Ideally, this would use FocusKeyboardEvent events as input, based on my keyboard focus PR: https://github.com/bevyengine/bevy/pull/15611
I believe classic React controlled components modify the value and then apply validation after
e.g. https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable from the React docs
I think scrolling support might be required, since it's hard to guarantee in all cases that text fits in a single box. For example, if you have a mobile MMO where players can send each other long letters.
Agreed, I meant to add that to the list and forgot
React components can also use prevent_default on the event, which we don't have support for.
You have it as a non-goal
Gah, added it to the wrong list
Sorry, was cutting and pasting from notes
Fixed it now
@tame aspen pin?
Not if they use onchange like in that example they can't. I think you'd have to use onbeforeinput to do that. Which one can certainly do on web, but I don't think that most controlled components are doing that. And I'd argue it's entirely orthogonal to whether a component is controlled or not. The key thing being whether the component state overrides the input state or not.
All right
The other benefit of uncontrolled is that it conforms to most programmers expectations of how text editors work
I figure it shouldn't be that hard to build an editor abstraction that supports either style
However, I tend to only use controlled widgets in my designs - not just text inputs, but checkboxes, any kind of widget really
Certainly in React you have the option of controlled or uncontrolled text input, with slightly different APIs.
Oh, I definitely use controlled too
With respect to cosmic-edit: I can see that cosmic's Buffer and CursorMotion have most of the methods we would need. What advantage would you see in using Editor?
I'm thinking of a TextInput component which would:
- Accept FocusKeyboardEvents and consume the ones it cares about (propagate(false)), allowing other keystrokes to propagate.
- Note that in single-line mode, arrow up/down would be allowed to propagate upward
- Emits TextChange events which bubble up
- Has methods for setting / replacing the current editor state.
- Has methods for getting the selection rect coords (with discontinuous rects for bidi) and cursor coords.
- Uses cosmic Buffer and CursorMotion for most editing functions.
Note that on some platforms, like consoles, text input is just a button which pops up a console-specific text input overlay. This swap should probably be done at a higher level, possibly with a feature flag.
Note that nothing I have said references reactivity, but it's perfectly compatible with reactivity. Just like in React, the "reactive text input" is a wrapper around the native text input DOM element, so too here: the reactive text widget would be a wrapper around this.
Couldn't there be two versions of text input, one single line, and one more feature full
I'm thinking inventory searching, naming pets or cities, and that sorta thing. Typing in a password, username and server
Making it more advanced could be a follow up, I'm scared of adding too many requirements that would delay inclusion.
I mean, manual focus control and such could be implemented with picking or other mechanics for now as well, so isn't really required for an MVP either
multiline isn't much more complicated really though, it's much easier than you'd think
I agree though it's better to start simple
It probably is, but it also needs to handle scrolling and wordwrapping, as well as letting users type in the enter character etc.
And a single line has another set of features is cares about. Such as typing in passwords as asterix, or only allowing for maybe numbers or whatever 🙂
And I believe more games and such would be unblocked by the single line branch of text-input 🙂
yeah I mean scrolling and wordwrapping as well, if you've got a single input it's actually pretty trivial to turn it into to a multiline one
getting a numeric only input was actually harder when I did it for some reasons
we had endless bugs with the numeric inputs
Yeah I agree that single-line is a self-contained feature set.
Single-line does need horizontal scrolling though
I wouldn't say need, not at first at least.
Many of my intended use cases would probably treat that as an edgecase without any meaningfulness in practise
Sure, the first MVP doesn't require it. But the design should take into account it will be implemented.
True
It should not block such development no, but neither should the lack of that feature block including it into the engine
What I am hearing is that there are not too many disagreements to the rough spec that I've outlined.
For the next step, I could do one of several:
- Collect the various design points in a hackmd
- Comment on the original ticket
- Close the old ticket and start a new one and discuss there
If this was the spec you meant, could this not be the first draft pr even? 🙂
Sure
As far as implementation, I would want to get the keyboard focus stuff accepted before any work begins on text editing. Well, I suppose we could use placeholder events.
The keyboard focus stuff is super simple, it just adds bubbling keyboard events. Then once it's in:
- Integrate with bevy_a11y
- Integrate with LWIM
- Pluggable extensions for tab navigation
- Text edit widget
I just migrated bevy_reactor to the latest Bevy mainline, one thing I noticed is that vertically centered text is still too low, I filed a bug: https://github.com/bevyengine/bevy/issues/15971
i'm looking at the 0.15 rc migration now. there's a place where i'm storing a Text so that i can spawn a (rich) text entity later. given that Text is now split into potentially many separate components / entities and there's no bsn yet to store it all as "data", is there an alternative that i'm missing or would i have to define my own rich text type?
||(or redesign so this isn't necessary--which i was planning on doing anyways for other reasons--but i'd prefer to migrate directly first)||
Hard to say how to best migrate this without more context, but it seems like you could store a Vec<TextSpan> and then spawn those as children of a Text component whenever you're ready to render the text
TextSpan only contains String, so no font / color specified
a simpler example i remembered now is a bbcode-like markup parser that returns the rich text data as a Text
Theres also nothing stopping us from creating a RichText(Vec<RichTextElement>) component, that is internally driven by the same systems. But I don't want to go down the "zoo of variants" route unless we have very good reason to
yeah ultimately this is the "i want to pass around a hierarchy of entities as data" problem, which will be solved in the general case by bsn
We can also re-evaluate the component splits / combine everything back into TextSpan if this is a regular sticking point. The split was done for perf / change detection reasons, but we can probably optimize in other ways
I'm very bullish on "make simple combined component APIs whenever possible" to improve UX
I feel like the split many-entity API is a good one for many use cases (e.g. as a target for reactivity libs). But that the underlying cosmic-text Buffer implementation ought to be public for use cases that want to bypass it (primary ones being alternative high-level APIs that have their own representation - bbcode parser, markdown parser, etc)
Agreed. TextBlock was intended to be closer to this "unopinionated driver" in my initial design, but the impl that landed ended up being a bit more hard coded. Breaking that out shouldn't be too hard
I also think the current TextUiWriter / TextUiReader API usage should largely be replaced by marker components to take advantage of the fact that these are entities / avoid the need to peck around with indices.
But right now that would often be a UX downgrade because it would require a separate query for each marker. Doesn't scale well to many text entries.
Feels like we're missing an API like:
commands.spawn(Text::default())
.with_child((TextSpan::new("Hi"), Hi))
fn system(text_spans: Query<&mut TextSpan>) {
text_spans.with::<Hi>().single_mut().0 = "hello".to_string();
}
This would be safe (as it isn't accessing component data just checking existence). Doing this inline would have performance concerns though, as it would require scanning all archetypes in the query for the existence of the component.
But right now that would often be a UX downgrade because it would require a separate query for each marker
Forgot to mention that these queries would conflict with each other by default if we're setting text values, making the UX even worse
We could optimize this with pre-filters:
commands.spawn(Text::default())
.with_child((TextSpan::new("Foo"), A))
.with_child((TextSpan::new("Bar"), B))
fn system(text_spans: Query<&mut TextSpan, Or<With<A>, With<B>)>) {
text_spans.with::<A>().single_mut().0 = "hello".to_string();
text_spans.with::<B>().single_mut().0 = "world".to_string();
}
But the redundancy / boilerplate there still makes me a bit sad.
I'm thinking something like this might be closer to the "ideal" API in terms of an ergonomics / performance balance:
commands.spawn(Text::default())
.with_child((TextSpan::new("Foo"), A))
.with_child((TextSpan::new("Bar"), B))
fn system(text_spans: Query<&mut TextSpan>, a: Marker<A>, b: Marker<B>) {
text_spans.with(a).single_mut().0 = "hello".to_string();
text_spans.with(b).single_mut().0 = "world".to_string();
}
Seems like this may have intersections with Query join infrastructure / maybe we could build on top of that
Notably Marker<A> would behave like (or maybe even alias to) Query<Entity, With<A>>
Which means it would cache the matching archetypes and avoid the need to compute them each time at the call site
You can achieve this:
fn setup(mut commands: Commands) {
commands.spawn(Text::default())
.with_child((TextSpan::new("Foo"), A))
.with_child((TextSpan::new("Bar"), B));
}
fn system(mut text_spans: Query<&mut TextSpan>, mut a: Marker<A>, mut b: Marker<B>) {
text_spans.with(&mut a).query().single_mut().0 = "hello".to_string();
text_spans.with(&mut b).query().single_mut().0 = "world".to_string();
}
Using:
type Marker<'world, 'state, T> = Query<'world, 'state, Entity, With<T>>;
trait QueryExt<'world, 'state, D: QueryData, F: QueryFilter> {
fn with<F2: QueryFilter>(&mut self, query: &mut Query<'_, '_, Entity, F2>) -> QueryLens<'_, D, (F, F2)>;
}
impl<'world, 'state, D: QueryData, F: QueryFilter> QueryExt<'world, 'state, D, F> for Query<'world, 'state, D, F> {
fn with<F2: QueryFilter>(&mut self, query: &mut Query<'_, '_, Entity, F2>) -> QueryLens<'_, D, (F, F2)> {
self.join_filtered(query)
}
}
But I'm not sure if it's that much of an ergonomics win...
There are four sets of use cases I think we need to be concerned about:
- Simple text: button with a "Load" or "Save" label, possibly localized. I think this use case is covered pretty well.
- Text-from-markup: Slightly less ergonomic today, but possible. Most users won't care, the only ones that do are the ones who are actually writing the conversion, which should ideally be something in a crate which is only implemented once.
- Basic unstyled text editing, equivalent to HTML
inputortextarea. Used by games for character names, save games, password login; used by editors for naming assets, labeling scenes and so on. This is not well supported today, but likely will be its own dedicated component, separate from TextBlock, that is provided by Bevy. - Advanced editing (rich editing, syntax highlighting, etc.) - I think this is pretty much out of scope for Bevy, if someone wants to work on it fine but it's not something I would prioritize.
I basically agree that the advanced use cases are out of scope for bevy core. But it would be good if there was an integration point for input, focus, rendering, etc so that 3rd-party libs can provide it.
I also think it would nice if the "text-from-markup" cases (of which there are many) could skip the text-as-entities API for performance reasons. It's not great to be repeatedly allocating bevy entities (each time the content changes) just because the underlying text layout functionality isn't exposed.
How often do you anticipate the content changing? I think most dynamic text is likely to be simple text spans, whereas most markup is text going to be preauthored content. There is also the "template" case which is a mix of markup and dynamic fields, but in that case the template system ought to be smart enough to replace only the entity that represents the dynamic span.
Input and focus are separate issues, and I think we will (soon) have good solutions for these.
Markup does not need to reallocate entities on change. Existing entities can usually be fully reused.
Ooh nice. Yeah not a huge ergo win at that point. And the performance of query joins is such that I expect the scan over matching TextSpan archetypes in the "ideal ergonomics" case to be in the same ballpark as the join (for finding a single matching entity). But it does solve the "peck around with text writer" problem.
Feels like there has to be a "nice" solve for this, somehow
Maybe even a method that converts this:
text_spans.single_with(&mut b)
To this:
text_spans.with(&mut b).query().single_mut()
(this would have ownership / scoping issues I think)
Yeah I tried returning a Query directly but not possible due to returning a reference to a dropped temporary.
Ok I'm thinking a solid "generic solution" is to:
- Implement "uncached" queries that are "just" the generated WorldQuery::State. Iterating such a Query involves checking each archetype for matches
- Implement CachedQuery::join(UncachedQuery) to allow us to filter down the uncached iteration over all archetypes to the set of cached matching archetypes.
- Implement
JoinedUncachedQuery::into_single_mut(), which consumes the JoinedUncachedQuery and returns a reference to a single matching element (by consuming, it allows us to return the single result).
Then we should be able to implement:
text_spans.single_mut_with::<A>()
which internally is
// uncached_query_filtered returns an Option because it uses WorldQuery::get_state(&text_spans.world.components) under the hood
let uncached_query = text_spans.uncached_query_filtered::<(), With<A>>().unwrap();
text_spans.join_uncached(uncached_query).into_single_mut()
People have been wanting (1) to enable things like for foo in world.iter_query::<&Foo>() { }
We also have a basic component index so finding all archetypes for &Foo is pretty fast. Having a cleaner separation in the code between the iteration logic/data and the archetype/table matching logic/data is generally a good move for future ECS improvements IMO.
Agreed
will uncached queries work with the scheduler or will they need to be used from exclusive systems?
Because they wouldn't cache their access, it would need to be computed on the spot. That would require some changes I think.
They could still be used in normal systems, in the context of a filter on an existing query (like the example above)
However I can't really think of a reason to not cache a query specified in a system parameter
So I don't think we need to build the infrastructure to support fn system(q: UncachedQuery<&mut A>) {}.
They could still be used in normal systems, in the context of a filter on an existing query (like the example above)
We would of course need to be careful that we don't inadvertently expand access. Ex: by only using it internally for methods likequery.single_with::<A>()
@warped jolt Is this correct?
#[require(Node, TextLayout, TextFont, TextColor, TextNodeFlags)]
pub struct Text(pub String);
I thought that Text was supposed to represent a single span? I'm having trouble constructing paragraphs because it's trying to lay out each span as a separate node.
Here's what I am working on BTW @mental tree @warped jolt
Text is the "rendered text UI node", which can be standalone / single span. TextSpan contributes its span to a parent Text
In that case, the docs are inconsistent:
/// [`TextBundle`] was removed in favor of required components.
/// The core component is now [`Text`] which can contain a single text segment.
I would say "incomplete" over inconsistent. Text is arguably the core component in terms of replacing TextBundle, and it does contain a single text segment
It would be helpful to mention TextSpan there as well, given that it is a part of replacing TextBundle and would help provide more context on the system's usage
OK so I switched to using TextSpan and weird stuff is happening - when I shrink the window, the words at the end of the line simply disappear instead of wrapping - I think that the paragraph height is not getting recalculated.
hmm. yeah its possible you found some sort of change detection bug or something
Are you spawning everything at once or is this getting dynamically inserted at various points via reactivity?
If this is a bug, getting a non-reactive repro would be a good place to start
I hand-checked most of the text examples during my review. But I think they're pretty static / spawned up front in general
There are measure func bugs that can cause text nodes to be missized
if you remove any constraints from the text node itself and set them on a wrapping parent node it might help (or not)
try adding a system to mark all text components as changed to trigger a recompute every update
The text_wrap_debug example is working fine so it might be something about your setup.
I am spawning everything once. The problem arises when I resize the window.
There are no reactions in the demo.
OK, I changed Text to TextSpan in one more place and now it's working, although I am somewhat confused as to why. That is, I understand the mistake I made, but not why it caused that specific result. The mistake was in using Text rather than TextSpan for the soft line break character, for which I simply insert a space character.
In any case, the goal of this experiment was "how hard is it to hook up a markdown parser to produce bevy_ui/text nodes?" and the answer is, "About two hours of coding". (For basic functionality, no code blocks, inline images, or tables, etc.)
Any Text components inside a text block hierarchy will not be included in the text block. They will either be invisible if not contiguous in the hierarchy, or they will be treated as children of the text node.
I would expect that - what I would not expect is for it to change the word-wrapping behavior of the other TextSpans in the parent block.
Maybe having child nodes of a text node borks text measurement.
Might be caused by the child text node's linebreak setting affecting measurement of the parent during layout.
In any case, it would be fairly easy to split this out as a separate bevy_ui_markdown crate which does not depend on bevy_reactor at all, but which does have a dependency on pulldown_cmark. The only issue is custom styling: a game which wants to have quest descriptions or character bios is going to want to be able to specify the font, text color, and so on. We don't yet have a consensus solution on how to do portable styles, that is, abstract styles that can be passed around as parameters. My approach in bevy_reactor is that "styles are functions", but they require a complex infrastructure that creates the illusion of a fluent API on top of component insertions and removals.
I'd rather design a custom markup language from scratch than try to contort markdown into a game-friendly shape.
I want to design one similar to bbcode when I have the time, with support for localization (with the expectation that all localization strings will be parsed with the markup engine) using that number-tag approach you mentioned a while back. So a localization entry might be fancy-count = [1]Count:[\1] [2]{count}[\2] and then 1 and 2 need to be assigned in-code like localized.set_tag(1, "b") (and {count} would be a variable you can assign using the fluent framework).
Yes, markdown is a document authoring system designed to be human readable, not a text formatting system or a general system designed to be machine readable. What you actually need is a way to arbitrarily annotate/mark/tag certain spans of text.
There are many projects that have trod this ground to draw inspiration from.
For example, asciidoc custom roles https://docs.asciidoctor.org/asciidoc/latest/text/text-span-built-in-roles/:
We need [.line-through]#ten# twenty VMs.
A [.myrole]#custom role# must be fulfilled by the theme.
Another example is the project https://github.com/mbakeranalecta/sam
In {Rio Bravo}(movie), {the Duke}(actor "John Wayne" (SAG)) plays a union colonel.
Maybe that looks like overkill for common cases like bold, italic and hyperlinks and it feels like you're missing the ease of markdown. Well, what all of these projects effectively do is provide syntax sugar. That is:
**this is bold**
effectively desugars to
{this is bold}(bold)
If you're married to Markdown, one more recent proposal that exists is Semantic Markdown https://hackmd.io/@sparna/semantic-markdown-draft#What-is-Semantic-Markdown
My name is
[Manu Sporny]{:name}
and you can give me a ring via
[1-800-555-0199]{:telephone}.
To be honest I think it's a major oversight for fluent, being built for rich user-facing applications, not to provide some means of arbitrarily tagging spans of text built in to it.
As for the number-tagged system [1]Count:[\1]:
-
I prefer syntaxes that don't duplicate the "marker" (i.e. I prefer something like
{Count:}[marker]ormarker!{Count:}. -
Numbered tags feels very "brittle". You write
localized.set_tag(1, "b")and later someone comes across and decides they want to uselocalized.set_tag(2, "b")orlocalized.set_tag(1, "a"). This is the same problem as you have with magic numbers, essentially. Named tags feels better as they can be self documenting. -
There should ideally be a way to inject additional data. Think hyperlinks: you need to specify a URL. Another example is coloured text: you need to specify a colour.
-
They should ideally be able to compose, e.g.
The {Bevy game engine}[bold, color(#ececec), url(https://bevyengine.org/)] is a refreshingly simple data-driven game engine
The goal of the numbered tags is to ensure the localization files don't contain any formatting logic, so translators don't have any power to add crazy formatting (or e.g. exploit a DoS vector if for example a super large font size causes system crash; also hyperlinks are very vulnerable). I also don't love the tag duplication, and named tags are a good suggestion (although need to be careful to disambiguate tags from actual settings like bold/b).
Ah yes, there's also Amazon's New World, where you could crash other people's clients by sending a badly-formatted item link in chat. Good times... 🙂
Maybe The {Bevy game engine}[@arbitrary_tag1 @arbitrary_tag2] is a refreshingly simple data-driven game engine would work (simplify: no commas, the () functional syntax is a good idea). Then the first pass to parse a localization string replaces tags with values registered in rust, and discards any non-tag stuff in the original string.
Can this markdown system support tags with custom behaviours like [on hover: show item tooltip] or [on click: open inventory and highlight a specific item]?
It would also be neat to be able to insert icons into text using tags eg. a picture of a sword before a damage value
It might be possible to do so directly with function reflection. I expect you'd need to manually register a 'function id'-to-system callback mapping in the markdown component. Then the markdown component will auto-pipe that system to the correct entity.
So like:
let mut mt = MarkupText::new("My {cat}[on_hover(sparkly cat_info)] is so cuddly!");
mt.add_animation("sparkly", sparkly_text_effect); // Not sure exactly.
mt.add_callback_with_cleanup("caf_info", spawn_cat_info, cleanup_cat_info);
Actually would probably use a different approach for animations since for that you often have multiple different things for hover/press/etc. and it's easier to package them separately.
What I did for bevy_mod_bbcode is to allow the user to register custom marker components which can then be added to the text entities.
For example, with [m=foo]test[/m], it could insert a custom Foo marker component and the user can use it to implement hover logic or any other custom logic on the text
Without duplicated tags how would you support overlapping styles as in
[b]This [i]example[/b] sentence[/i]
I personally chose not to, forcing users to do
[b]This [b,i]example[i] sentence[] back to default
I'm also storing "styles" associated with b and i as entities and cloning their components onto text spans. Users just spawn them.
commands.spawn((
RegisteredStyle("b"),
TextFont { ... },
TextColor(...),
SomeMarkerComponent
));
This can all be done without macros. Everything must be Reflect though. Do whatever fancy stuff you want in normal systems.
I'm still not totally sure if I like the "end-tag-less" approach though.
Having the style data separated from the tag might be beneficial in cases where you want to apply the same style to multiple different areas of the text. That way you don’t need to duplicate the style data within the text each time you use that style and it can easily be updated in one place
I offer built in tags like b and i which are handled separately. These two essentially do the same thing: Changing the font query. So it was easier to special case them instead of trying to handle all cases with marker components.
As for the custom tags, I "flatten" the tag structure into spans, duplicating the markers as necessary
Just updated bevy_mod_bbcode to Bevy 0.15.0-rc.2 and it was surprisingly straightforward, even with the draft migration guide!
Once it compiled it just worked again.
The hierarchical text layout is quite nice for this plugin, I actually already used just one span per Text already to emulate text-as-entities, so having it less hacky now is nice.
Also required components allowed me to remove a bunch of boilerplate.
Here's the update PR if anyone's interested: https://github.com/TimJentzsch/bevy_mod_bbcode/pull/17
Great work y'all!
Just wrote 0.15 release notes for cosmic text. Let me know if I missed anything!
https://github.com/bevyengine/bevy-website/pull/1808
@tame aspen and others: if anyone is interested, the latest version of my "inheritable text styles" abstraction can be found here: https://github.com/viridia/thorium_ui/blob/main/crates/thorium_ui_controls/src/text_styles.rs - this includes both a trigger hook to compute the text style when the text entity is first inserted, as well as a system which keeps text styles up to date when inherited style definitions change. This behavior requires opting-in via a marker component.
This also includes the "either a handle or an asset path" functionality we discussed.
Excellent, that's pretty much exactly the sort of design I would like to see for heritable style
You could use immutable components or OnMutate observers for keeping synced instead in the future, rather than change detection
But that's just a performance question really
I think the main performance issue is that changing an inherited style high up in the tree can potentially affect an unknown number of descendant entities. However, in practice that never happens, generally text styles high in the tree are set once and never changed.
The biggest issue is going to be the O(n) performance of the current change detection impl
It's really nice to be able to set the font handle once at the root of the tree and then never have to worry about it.
But all sorts of users want that improved anyways
Don't have time to fully debug right this second, but I'm seeing a bizarre text issue where
- text
- textspan, textcolor, textfont
- textspan, textcolor, textfont
does not work (no text displayed at all), but
- text, textcolor, textfont
- textspan, textcolor, textfont
- textspan, textcolor, textfont
does. possibly only in situations where a mix of the default / non-default font handle is used
i suspect that it might be related to https://github.com/bevyengine/bevy/issues/16406, need to test with the fix cherry-picked
This is expected - TextSpanIter only iterates over contiguous 'full text' entities.
It works fine and our examples even use that format. Except when there's an invalid font handle in any of the spans. Documented better here:
https://github.com/bevyengine/bevy/issues/16521
Text requires TextColor & TextFont so the examples are using your second case.
I diagnosed the issue, details in issue linked above.
Nice
i'm curious about the text rework having TextColor and TextFont components separate from the primary Text component after reading this design guideline:
Prefer simple APIs / don't over-componentize. By default, if you need to attach new properties to a concept, just add them as fields to that concept's component. Only break out new components / concepts when you have a good reason, and that reason is motivated by user experience or performance (and weight user experience highly).
i assume the exception is for a good reason but it wasn't explained in the release post
and then contrasted with Sprite and UiImage's optional components (texture atlas and scale mode) which became Option fields
I don't know the reason, but I am in favor of it generally. I think it simplifies both style inheritance calculations and animated text styles.
Separating them avoids issues with change detection when animating TextColor.
Yeah this was the rationale. I did want to revisit this at some point though, as I do think a combined component is the better UX
Seems like we could find ways to avoid text recomputes without relying directly on change detection
(or solely on it)
I would say let's revisit this after we get support for styles / font-families, since that's going to impact the text components (bold, etc.)
Ex: cache the "color since last check" on a computed component
Makes sense to me!
What's the status of this group now that we have cosmic text and the new font api?
Is the work complete?
There's things like system fonts, text input, markup, text outlines, bug fixes, although there's not necessarily a focused goal for the working group atm.
Yeah I'm tempted to close this out and move to #ui-dev ?
All of those things need to be done, but I think the big scary bit is complete
There is useful backlog here, so as long as chat history is kept around, it should be fine
Yep!
The prime disadvantage of working groups is that you can't make threads. However, working groups have a huge advantage over threads, which is that they don't get lost.
I think that font families warrants a design doc, but if that gets too unruly we can always make a new working group 🙂
Everything else is pretty small thankfully
I suspect that font-families will entail the largest conversation
Font management aside, I'll be promoting a copy paste from this file https://github.com/UkoeHB/bevy_cobweb_ui/blob/main/src/bevy_ext/fonts.rs. It implements CSS fonts and the CSS font-matching algorithm.
cosmic-text now exposes outline curves (not sure which version that happened in), so some low hanging fruit would be to expose that, then potentially build a mesh-text backend (e.g. using lyon): great for reasonably high fidelity billboards in 3d. additionally, we could now apply transformations to the curves so we can do skew, scale, rotation, etc. and more word-art style things, maybe even text outlining and shadows, however that's rendered: if that were supported in a 2d rasterized context, we would probably need to revisit the idea of opting out of glyph caching so we don't run into the original issue around font resizing.
alternatively maybe the future is in a vello-based or inspired compute shader-based renderer, which kinda does it all, except I guess 3d (though maybe that just needs some transformations to make that work)
Finally migrating one of my more text-heavy apps. So far this sort of stuff seems like the least fun part.
fn update_target_text<R: TextRoot>
It's much more annoying to deal with text that may be a Text2d or a Text.
yeah, i have found that annoying as well.
can you put both a Text and a TextSpan on the same entity?
I dunno, sounds scary.
my point is more, maybe Text and Text2d should be markers and TextSpan reserved for content.
Currently we are mixing behaviors in a weird way
Oh, yeah, that's also where my had was at
(my ideal world would be TextUi and Text2d + Text content but i know that's not going to happen)
But that idea seems like something would be called a "ux regression"
it's mildly annoying that the more common type has the longer (less discoverable) name as well.
feel like people are going to try and make a tree of Text, rather than Text root + TextSpan nodes.
I wonder if TextWriter could be rewritten with stuff like Query<(Option<&mut Text>, Option<&mut Text2d>), With<Or<(Text, Text2d)>>)> or whatever. Gross, but the grossness would be contained in bevy.
Cosmic text has always had that I think. At least since before bevy started integrating it. It's just tricky to do high-quality glyph raterisstion, so the swash-based rasterizer is appealing.
This was in an older design, but simplified by cart
the end result is that we now have 3 (..n from the ecosystem) components that all behave like text
seems kind of contradictory to the direction of required components imo
For text folks : please consider https://github.com/bevyengine/bevy/issues/16901 thanks! 🙂
You would then need to specify both when spawning text. That’s the tradeoff made and imo it’s better to prioritize nice spawns over easier cross-context edits
Cross context edits are a less common scenario, and when it’s being done it’s probably by a generalized “text editing system” rather than something specific. I’m fine with foisting the comparatively small complexity on those cases
When compared to making every single text spawn in bevy significantly worse
I feel like improving Bevy's glyph storage wouldn't be controversial.
Well, the current arrangement does seem to make "garbage collection" trivial. I feel like we'd still want separate atlases per font handle at least.
Seemed relatively simple to get down to one set of atlases per font. You doing anything fancier than this? https://github.com/rparrett/bevy/commit/798d456403fd6a216b3b98ca4475d590d62ff6bc
Configurable or just increased atlas size is probably an easy win too
Not personally seeing any downsides to this
Ok that makes a ton of sense
Yes. I have an immediate mode style library, aiming at single draw call and minimum amount of texture, so I'm packing everything I can no matter the font family or size or style.
I reckon it shouldn't be too difficult and unlikely to be a performance bottleneck if we abstracted the storage away from the text shaping and layouting, no?
Maybe? It feels like what you’re doing could be a trade off not everyone wants, so it makes sense to me in that regard. I’m just trying to understand that trade off because improving things by default for all users would be good.
I think others probably want to plug in their own text renderers as well.
Ah, glyphs with TextFont { font_smoothing: false } need to be stored separately so that the underlying texture can have ImageSampler::nearest().
Allowing font atlases to grow in size seemed like an obvious thing to try -- the atlas allocator already supports this. But we didn't have a way of growing an Image without corrupting the image data.
https://github.com/rparrett/bevy/commit/d9d0022d83e0523ae8ee8e7dd8e6b14889b1aca1
Seems to work, but this branch is now built on a bad foundation given my realization in the message above.
Okay, done playing with this for now. Left some notes in the issue.
@tulip hinge or anyone else -- any idea if this is likely to be considered a cosmic bug?
https://github.com/pop-os/cosmic-text/issues/343
or are we going to have to do our own alignment after layout for text2d?
This smells like a cosmic-text bug to me
And aha, this is what I saw in my example from yesterday
currently travelling so can only really look at it on my phone right now but the logic seems right but I'd just look at whether visual_line.w is being correctly extended here, maybe some floating point arithmetic bug
https://github.com/pop-os/cosmic-text/blob/main/src/shape.rs
let line_width = match width_opt {
Some(width) => width,
None => {
let mut width: f32 = 0.0;
for visual_line in visual_lines.iter() {
width = width.max(visual_line.w);
}
width
}
};
let alignment_correction = match (align, self.rtl) {
(Align::Left, true) => line_width - visual_line.w,
(Align::Left, false) => 0.,
(Align::Right, true) => 0.,
(Align::Right, false) => line_width - visual_line.w,
(Align::Center, _) => (line_width - visual_line.w) / 2.0,
(Align::End, _) => line_width - visual_line.w,
(Align::Justified, _) => 0.,
};
Appreciate the response. As far as I can tell, layout is happening "line by line" so the "max width" in the pointed-at code is only relevant if a line gets wrapped onto multiple "visual lines."
There doesn't seem to be any mechanism for adjusting the entire text after layout is done.
So an unbounded buffer with line breaks just doesn't seem to be something cosmic is doing anything with. More of a missing feature than a bug, maybe?
I'm not even sure if it's possible to implement on the cosmic side.
In Parley I made alignment a seperate phase that the caller can pass a seperate width constraint to
I think it would be possible to do inside the library. But designing the interface would be tricky and making it a separate call was easy.
Okay yeah, that's where my thinking was headed too -- I have no idea how to many something like that work with the incremental layout stuff that's going on in cosmic.
Might just do it on the Bevy side though, unless cosmic authors see this as a bug and/or wanted feature and have a design in mind.
I'm actually fairly certain @near moon wrote the code we need at some point, lol.
Yeah I had to make a lot of post layout adjustments to work around different ab_glyph bugs before
I don't think this a bug, it's just a decision they are taking about what to do when there are no text bounds.
You can't center text within an infinite horizontal space so you can either left align everything or center it by using the width of the longest line as the new bounds. ab_glyph did the latter.
Both options make sense though
The text2d problem isn't a bug, the Text2d API is just really confusing to use. The way to fix the user's problem is to set a large width text bound withAnchor::Center
cosmic-text attempts to do the latter here too
Oh does it
so it does seem like a bug
yeah see the first snippet is getting the width of the max line if buffer width is set to None. second snippet is using the width
the reason we'd want to make cosmic-text's work is so that the built-in hit detection works as expected, for mouse interactions
yeah, otherwise it would be simple to calculate the visual width again in bevy and center the text ourselves
yeah and i definitely recall the ab_glyph implementation did this, and my initial cosmic-text implementation carried that onward (when previously cosmic-text didn't take an Option<f32> width, just an f32 width)
but didn't play well with the hit detection
we could center the text ourselves and modify the mouse coordinates we send to cosmic-text, adjusted as though the text was still left-aligned
it's a bit of a hack but not difficult to setup
true
Yeah so it was pretty early in the morning but I see your point here now. It doesn't feel like it should be intentional though.
On the one hand, when you set the text in the Buffer, it asks for an alignment, implying you want the alignment to apply to the whole buffer.
On the other hand, each BufferLine has the ability to set an alignment, implying buffers can have mixed alignments.
like:
left align
right align
center align
I guess I was headscratching over how "post-layout alignment" would jive with the the e.g. shape_to_scroll "incremental" API, in the case of a buffer that has a vertical bound but not a horizontal one.
It seems like if you're not shaping the whole buffer, you can't calculate the max width to align stuff properly?
Yeah, but I think the unbounded layout was an afterthought to be honest
But
In Parley I made alignment a seperate phase that the caller can pass a seperate width constraint to
-- nico
Makes sense to me
right but does Parley have mixed alignment?
I think it might work fine with mixed alignment
applies to the whole Layout (equivalent to a Buffer) https://docs.rs/parley/latest/parley/layout/struct.Layout.html#method.align
Text layout.
which to me is more reasonable
it feels like there's a bit of a confusion between what I'd call "blocks" and "lines" in cosmic-text
I think I've been calling those (groups of "visual lines") and "lines" but possibly
I think we agree on definition of lines there
i mean like a paragraph, or a block layout, as a block
which is more like a Buffer
but in cosmic-text's case it's actually a BufferLine
it's sort of a mix
Ah okay
I've left a comment on the issue and pinged Jeremy so I guess we'll see what he says.
We could just go back to aligning everything to the left, then aligning the glyphs manually, as suggested by ickshonpe. But we won't have Justified text then (since that is word-aware), though I can't recall if we even support that.
Thanks! And, that's a good point, we do I think.
there is a simple fix
I think
just run text layout twice
you run it once unbounded, then run it back again with the text bounds generated by the first run
it's not very efficient but you only need to do it for non-left-justified unbounded text
ooh, clever
yeah, that seems to work. I'll open a PR soon.
I am also pretty confident that we are doing one additional shape_until_scroll that isn't necessary most of the time. And possibly like 2-3 more the first time the text is processed, or if the size, font size, or wrapping change. Most of these buffer.set_ functions can cause reshaping and/or relayout.
I'll test that theory after. I think that could be partially fixed on our end.
Here's the first one. https://github.com/bevyengine/bevy/pull/17270
Yeah, cosmic-text really ought to changed to shape lazily so that multiple write can be coalesced into one relayout.
oh wait bestRanar you're rparret? I had no idea hehehe
I know! It's confusing, right?
Yeah, sorry. Discord is still sort of a strange place to me, haven't really wanted to make any effort to tie the two identities together.
Another text alignment thing could use another review: https://github.com/bevyengine/bevy/pull/17365
Is there no longer a way to get the cosmic Buffer for some text? I thought we had that in an earlier iteration.
Seems necessary for picking: https://github.com/bevyengine/bevy/issues/17706 adds a way to get it non-mutably.
But also if you want to do a text input with a cosmic Editor you need a mutable ref.
The buffer cannot be mutated by user code because the text pipeline resets it every tick. You need a separate buffer that isn't owned by the pipeline.
It should not be needed for picking. TextLayoutInfo should be sufficient
From ComputedTextBlock::buffer:
/// This is private because buffer contents are always refreshed from ECS state when writing glyphs to
/// `TextLayoutInfo`. If you want to control the buffer contents manually or use the `cosmic-text`
/// editor, then you need to not use `TextLayout` and instead manually implement the conversion to
/// `TextLayoutInfo`.
Hm, I see. robtfm had something working in bevy_simple_text_input that created an Editor but still used bevy's copy of the Buffer. I'll have to look closer and try to understand what's going on there. I think it was syncing the state of the editor back to the Text afterwards or something. But this was based on the first cosmic PR.
re: picking, using cosmic's built-in hit detection would be nice... bytemunch had issues with the current state of TextLayoutInfo trying to do picking, but I didn't look close enough to see if they were surmountable in some other way.
#ui-dev message
Ick made a decent argument for not using it (hit) though
It's kind of convoluted and inefficient to use the editor to set text, which then resets the buffer. Better to just use the editor to set a buffer, then directly use that buffer to make layout glyphs. And save the text value in a custom component.
No need to cram all functionality into the existing text API. Text input is a huge project, so it will benefit from starting off on fresh ground and not tied to the non-text-input architecture.
Funny, someone just opened https://github.com/bevyengine/bevy/pull/17748.
Yeah okay, I think I get what you're saying but that's probably not an approach I want to take / maintain in bevy_simple_text_input. The goal is to make the thing less complicated if possible until Bevy gets its own text input and I can delete my little thing.
(And side-goal: enable other people to build fancier third party text inputs)
IMO the raw CosmicBuffer ought to be the primary text API in Bevy
Everything else (including the text-as-entities API) ought to be higher-level APIs on top
If that means breaking changes if/when Bevy switches to a different text backend then... so be it?
Maybe "primary" is overstating things. I think it ought to be a public supported API that other people are encouraged to build abstractions on top of. But with end-users encouraged to use one of those abstractions, be that the built-in one or some other 3rd-party one.
The only extra-complicated bit is making TextLayoutInfo. But you can re-use bevy's text pipeline logic for that (and I do think the text pipeline can/should be refactored so logic is reusable for custom pipelines).
Hm, cosmic maintainer's no longer around on this server?
Opened https://github.com/pop-os/cosmic-text/issues/362 over there to beg for a release.
cosmic-text 0.13.0 is now released!
I took a bit of a break from social media, but I'm back.
Thank you!
It seems like we've got a small but showstopping bug. I believe I've fixed it over in https://github.com/pop-os/cosmic-text/pull/365. Would you mind taking a look if you have a free moment?
Released as 0.13.2
Awesome, Thank you!
cosmic-text 0.14.0 is here, now with variable font feature support: https://github.com/pop-os/cosmic-text/releases/tag/0.14.0