bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.11k stars 3.45k forks source link

Improve ergonomics for single-section `Text`s #14854

Open Olle-Lukowski opened 3 weeks ago

Olle-Lukowski commented 3 weeks ago

What problem does this solve or what need does it fill?

With the changes introduced in the PR linked below, the common case for Text where there is only one TextSection gets a bit more annoying. This should be fixed ASAP, in any case, before 0.15.

Info

Old API:

commands.spawn(TextBundle::from_section(...));

Current API that will need improvements:

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

What solution would you like?

I think the easiest option is to make add a Commands::spawn_text() method, this is simply a shorthand for spawning one TextSection as a child of a Text.

What alternative(s) have you considered?

There are probably alternatives, and we should discuss those

Additional context

This is important once #14572 gets merged.

rparrett commented 3 weeks ago

Some additional background:

cart commented 3 weeks ago

Relevant conversation here: https://discord.com/channels/691052431525675048/1248074018612051978/1276270114517418116

NotAFile commented 2 weeks ago

I see another three options:

rparrett commented 2 weeks ago

To briefly summarize the linked discord conversation above, Cart is proposing essentially combining the concepts of Text and TextSection and allowing them to be nested. edit: I was confused, see next messages below.

This is a bit of a departure from the current minimal state of https://github.com/bevyengine/bevy/pull/14572, and #14791 is a prerequisite, though one could possibly imagine doing something similar with bundles.

But it seems that this is something we don't want to put off for later, so it is possible that this issue is redundant.

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

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

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

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

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

In that conversation we reached consensus that the last two cases above shouldn't behave that way (implicitly "flattening" the text layout in nested Text hierarchies).

Instead, the rule would simply be: by default, treat each Text as a separate / autonomous text section, laid out according to how it / its parent haven chosen. If a parent specifies Display::Flow, then all of its children will be laid out as if they are a group of back-to-back text sections.

cart commented 2 weeks ago

Pulling in @viridia, who was part of this conversation on discord.

viridia commented 2 weeks ago

Thanks for pinging me. Here's a more formal specification of what I proposed:

1) Canonically, text nodes are leaves of the tree. Any children of a text node are ignored by the layout. 2) The layout of a text node depends on the parent node's Display style. 3) For text nodes whose parent is Display::Grid, Display::Flex, or Display::Block, each text node is treated as a separate row/column/cell; the cosmic-text layout algorithm is performed on that single entity. There can be word wrap, but only if the text entity won't fit in the allotted space. 3) For text nodes which are children of a parent with Display::Flow (a new display type), all text and non-text children are word-wrapped together; adjacent text nodes are merged (which means ligatures can cross node boundaries). Non-text items are treated as unitary, non-breakable glyphs for layout purposes. A single cosmic-text layout context is allocated for the parent. 4) Cosmic-text layout contexts will be provided implicitly by the Bevy engine, users do not need to explicitly insert them as components. (How this is to be implemented is TBD).

This proposal is explicitly modeled after the behavior of HTML/SGML text blocks, but with significant simplifications (e.g. we don't strip leading and trailing spaces, the "display" property only affects internal, not external, layout behavior, etc. The Display::Flow display type corresponds to the internal layout rules of CSS display: inline.

viridia commented 2 weeks ago

As mentioned in the discord, the goal here is not to regurgitate the web or allow building of a web browser within Bevy; the goal is to permit document-like UIs such as the character bios or quest logs seen in games like Witcher 3 or Jedi: Last Survivor.

There is also the issue of styling, which is part of the problem of text-section ergonomics, but might need to be a separate issue. Individual text nodes can have styles, but there's no internal layout, so properties like display::Grid or gap are meaningless. However, style properties such as left or margin are external layout properties, and thus are meaningful, but might have different effects in the context of a Display::Flow parent. For text nodes in a grid/flex/block context, these layout parameters are applied to the bounding box of the text entity. For text nodes in a flow context, it's "whatever the closest equivalent that cosmic-text supports", otherwise we punt (in the short term anyway).

As some of you know, I am a fan of inherited styles, however there's a number of prominent users on Discord who don't like them / find them confusing. The compromise that I have implemented in Quill is to allow inheritance for only those styles which are text specific, of which there are just 5: font, size, weight, style, decoration. Other kinds of inheritance can be done in other ways, using reactive contexts or whatever. Being able to assign a text style to a parent and have that style apply to all of the individual text sections is very convenient and ergonomic - for example, my Button widget imposes a style for its children (which of course individual sections can override, but in practice never do). Style inheritance is implemented via a separate system - it requires a marker component to be inserted into the text node - that is independent from Quill.

UkoeHB commented 1 week ago

See discussion https://github.com/bevyengine/bevy/discussions/15014