Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI node outlines #9931

Merged
merged 15 commits into from
Oct 5, 2023
Merged

UI node outlines #9931

merged 15 commits into from
Oct 5, 2023

Conversation

ickshonpe
Copy link
Contributor

@ickshonpe ickshonpe commented Sep 26, 2023

Objective

Add support for drawing outlines outside the borders of UI nodes.

Solution

Add a new Outline component with width, offset and color fields.
Added outline_width and outline_offset fields to Node. This is set after layout recomputation by the resolve_outlines_system.

Properties of outlines:

  • Unlike borders, outlines have to be the same width on each edge.
  • Outlines do not occupy any space in the layout.
  • The Outline component won't be added to any of the UI node bundles, it needs to be inserted separately.
  • Outlines are drawn outside the node's border, so they are clipped using the clipping rect of their entity's parent UI node (if it exists).
  • Val::Percent outline widths are resolved based on the width of the outlined UI node.
  • The offset of the Outline adds space between an outline and the edge of its node.

I was leaning towards adding an outline field to Style but a separate component seems more efficient for queries and change detection. The Outline component isn't added to bundles for the same reason.


Examples

  • This image is from the borders example from the Bevy UI examples but modified to include outlines. The UI nodes are the dark red rectangles, the bright red rectangles are borders and the white lines offset from each node are the outlines. The yellow rectangles are separate nodes contained with the dark red nodes:

    outlines
  • This is from the same example but using a branch that implements border-radius. Here the the outlines are in orange and there is no offset applied. I broke the borders implementation somehow during the merge, which is why some of the borders from the first screenshot are missing 😅. The outlines work nicely though (as long as you can forgive the lack of anti-aliasing):

    image


Notes

As I explained above, I don't think the Outline component should be added to UI node bundles. We can have helper functions though, perhaps something as simple as:

impl NodeBundle {
    pub fn with_outline(self, outline: Outline) -> (Self, Outline) {
        (self, outline)
    }
}

I didn't include anything like this as I wanted to keep the PR's scope as narrow as possible. Maybe with_outline should be in a trait that we implement for each UI node bundle.


Changelog

Added support for outlines to Bevy UI.

  • The Outline component adds an outline to a UI node.
  • The outline_width field added to Node holds the resolved width of the outline, which is set by the resolve_outlines_system after layout recomputation.
  • Outlines are drawn by the system extract_uinode_outlines.

* The `Outline` component adds an outline to a UI Node.
* The `outline_width` field added to `Node` holds the resolved width of the outline, which is set by the `resolve_outlines_system` after layout recomputation.
* Outlines are drawn by the system `extract_uinode_outlines`.
…node's border, so they are clipped using the clipping Rect of their ui node entity's parent.
Copy link
Contributor

@nicoburns nicoburns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems useful as-is, but I wonder how hard it would be to add https://developer.mozilla.org/en-US/docs/Web/CSS/outline-offset support to this? Could be useful for people who want to draw outlines inside the node boundaries rather than outside.

@nicoburns nicoburns added C-Feature A new feature, making something new possible A-UI Graphical user interfaces, styles, layouts, and widgets labels Sep 26, 2023
@ickshonpe
Copy link
Contributor Author

Seems useful as-is, but I wonder how hard it would be to add https://developer.mozilla.org/en-US/docs/Web/CSS/outline-offset support to this? Could be useful for people who want to draw outlines inside the node boundaries rather than outside.

Really trivial, I'll add it now

* Added an `outline_offset: f32` field to Node.
* Added an `offset: Val` field to `Outline`.
* In `extract_uinode_outlines` the twice the outline offset is added to the node size before calculating
@ickshonpe
Copy link
Contributor Author

ickshonpe commented Sep 26, 2023

Also have outlines working with border-radius as well:

curved_outlines

/ ui_scale.0 as f32;

for (outline, mut node) in outlines_query.iter_mut() {
let node = node.bypass_change_detection();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come you're calling bypass_change_detection here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The text system queries for changes to Node so it can recompute the text layout on changes to the UI node's size.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, do we not care about detecting changes to the outline_width and outline_offset fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at the moment. Maybe we could have a separate component for every property but it doesn't seem very tractable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I'd put a comment here describing that we don't care about change detection for these fields because they're private and only used as a cache.

/ ui_scale.0 as f32;

for (outline, mut node) in outlines_query.iter_mut() {
let node = node.bypass_change_detection();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I'd put a comment here describing that we don't care about change detection for these fields because they're private and only used as a cache.

@wilk10
Copy link
Contributor

wilk10 commented Oct 1, 2023

Love this feature!

I have one question about the implementation: one of the main use-cases I can see would be to show the outline on the currently hovered element in a list or grid. In that case, as a user I would either be removing and adding the Outline component very frequently, or I guess I can set the widthto 0 or the value I want.

IMO it would be more explicit and discoverable to have an is_visible flag as field on Outline and add Outline to all relevant node bundles, so that then I can just toggle it. What do you think?

@nicopap
Copy link
Contributor

nicopap commented Oct 1, 2023

I think setting the outline color to transparent is clear enough as is. Maybe adding a doc line saying "Consider setting the outline color to transparent if you need to toggle the outline visibility" could fill the gap. Also the current node bundles are already way too large, and results in a lot of extra machine codes. It's reasonable to want to avoid adding more and more components to them.

Also, my thinking is that is_visible adds more concepts that are redundant with pre-existing ones (color), and therefore should be eliminated, as they add more concepts to deal with with no upsides.

@ickshonpe
Copy link
Contributor Author

Love this feature!

I have one question about the implementation: one of the main use-cases I can see would be to show the outline on the currently hovered element in a list or grid. In that case, as a user I would either be removing and adding the Outline component very frequently, or I guess I can set the widthto 0 or the value I want.

IMO it would be more explicit and discoverable to have an is_visible flag as field on Outline and add Outline to all relevant node bundles, so that then I can just toggle it. What do you think?

Setting the outline's color to Color::NONE makes the most sense to me for toggling outlines on and off. An is_visible method feels like it would be too easily confused with Bevy's existing visibility systems.

Most UI nodes won't use outlines, so it wouldn't be efficient to add it to every bundle. If you do need to create a lot of outlined bundles you can always use a builder method or a type alias for (NodeBundle, Outline).

@wilk10
Copy link
Contributor

wilk10 commented Oct 2, 2023

Love this feature!
I have one question about the implementation: one of the main use-cases I can see would be to show the outline on the currently hovered element in a list or grid. In that case, as a user I would either be removing and adding the Outline component very frequently, or I guess I can set the widthto 0 or the value I want.
IMO it would be more explicit and discoverable to have an is_visible flag as field on Outline and add Outline to all relevant node bundles, so that then I can just toggle it. What do you think?

Setting the outline's color to Color::NONE makes the most sense to me for toggling outlines on and off. An is_visible method feels like it would be too easily confused with Bevy's existing visibility systems.

Most UI nodes won't use outlines, so it wouldn't be efficient to add it to every bundle. If you do need to create a lot of outlined bundles you can always use a builder method or a type alias for (NodeBundle, Outline).

Thanks for the reply. I understand and setting the color to Color::NONE should work and be clear enough. Does it make sense to add methods on Outlineto show / hide (or draw or other alternative names) to control color, in order to point the user in the right direction? Maybe at least a comment on the color field?

My main concern regards table moves when the Outline component is added / removed frequently (eg: an inventory system implemented as a grid, where only the hovered element needs an outline). Setting the color to Color::NONE is clearly the better solution than inserting / removing the component, IMO.

@nicopap nicopap added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Oct 2, 2023
@ickshonpe
Copy link
Contributor Author

ickshonpe commented Oct 2, 2023

Love this feature!
I have one question about the implementation: one of the main use-cases I can see would be to show the outline on the currently hovered element in a list or grid. In that case, as a user I would either be removing and adding the Outline component very frequently, or I guess I can set the widthto 0 or the value I want.
IMO it would be more explicit and discoverable to have an is_visible flag as field on Outline and add Outline to all relevant node bundles, so that then I can just toggle it. What do you think?

Setting the outline's color to Color::NONE makes the most sense to me for toggling outlines on and off. An is_visible method feels like it would be too easily confused with Bevy's existing visibility systems.
Most UI nodes won't use outlines, so it wouldn't be efficient to add it to every bundle. If you do need to create a lot of outlined bundles you can always use a builder method or a type alias for (NodeBundle, Outline).

Thanks for the reply. I understand and setting the color to Color::NONE should work and be clear enough. Does it make sense to add methods on Outlineto show / hide (or draw or other alternative names) to control color, in order to point the user in the right direction? Maybe at least a comment on the color field?

My main concern regards table moves when the Outline component is added / removed frequently (eg: an inventory system implemented as a grid, where only the hovered element needs an outline). Setting the color to Color::NONE is clearly the better solution than inserting / removing the component, IMO.

I wouldn't consider the API here final by any means.

I understand your concerns about table moves. As well as the performance issues, at the moment in Bevy UI they can affect the order of UI elements atm in some circumstances (this should be fixed soon though, we've fixed some of the problems already, and I've been working on changes that should fix the remaining issues).

Show/Hide helper methods have similar problems to the is_visible method though, it feels like they could be confused with the Style property settings. I added what you suggested to the color field's description and some examples to the struct's doc comments that should help I think. Maybe we could add an outlined buttons example in a separate PR as well.

@alice-i-cecile
Copy link
Member

@ickshonpe once merge conflicts are resolved I'll merge this.

auto-merge was automatically disabled October 3, 2023 11:26

Head branch was pushed to by a user without write access

@ickshonpe
Copy link
Contributor Author

this is ready to merge

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Oct 5, 2023
Merged via the queue into bevyengine:main with commit 2e887b8 Oct 5, 2023
21 checks passed
@cart cart mentioned this pull request Oct 13, 2023
43 tasks
regnarock pushed a commit to regnarock/bevy that referenced this pull request Oct 13, 2023
# Objective

Add support for drawing outlines outside the borders of UI nodes.

## Solution
Add a new `Outline` component with `width`, `offset` and `color` fields.
Added `outline_width` and `outline_offset` fields to `Node`. This is set
after layout recomputation by the `resolve_outlines_system`.

Properties of outlines:
* Unlike borders, outlines have to be the same width on each edge.
* Outlines do not occupy any space in the layout.
* The `Outline` component won't be added to any of the UI node bundles,
it needs to be inserted separately.
* Outlines are drawn outside the node's border, so they are clipped
using the clipping rect of their entity's parent UI node (if it exists).
* `Val::Percent` outline widths are resolved based on the width of the
outlined UI node.
* The offset of the `Outline` adds space between an outline and the edge
of its node.

I was leaning towards adding an `outline` field to `Style` but a
separate component seems more efficient for queries and change
detection. The `Outline` component isn't added to bundles for the same
reason.

---
## Examples

* This image is from the `borders` example from the Bevy UI examples but
modified to include outlines. The UI nodes are the dark red rectangles,
the bright red rectangles are borders and the white lines offset from
each node are the outlines. The yellow rectangles are separate nodes
contained with the dark red nodes:
 
<img width="406" alt="outlines"
src="https://github.com/bevyengine/bevy/assets/27962798/4e6f315a-019f-42a4-94ee-cca8e684d64a">

* This is from the same example but using a branch that implements
border-radius. Here the the outlines are in orange and there is no
offset applied. I broke the borders implementation somehow during the
merge, which is why some of the borders from the first screenshot are
missing 😅. The outlines work nicely though (as long as you
can forgive the lack of anti-aliasing):


![image](https://github.com/bevyengine/bevy/assets/27962798/d15560b6-6cd6-42e5-907b-56ccf2ad5e02)

---
## Notes

As I explained above, I don't think the `Outline` component should be
added to UI node bundles. We can have helper functions though, perhaps
something as simple as:

```rust
impl NodeBundle {
    pub fn with_outline(self, outline: Outline) -> (Self, Outline) {
        (self, outline)
    }
}
```

I didn't include anything like this as I wanted to keep the PR's scope
as narrow as possible. Maybe `with_outline` should be in a trait that we
implement for each UI node bundle.

---

## Changelog
Added support for outlines to Bevy UI.
* The `Outline` component adds an outline to a UI node.
* The `outline_width` field added to `Node` holds the resolved width of
the outline, which is set by the `resolve_outlines_system` after layout
recomputation.
* Outlines are drawn by the system `extract_uinode_outlines`.
ameknite pushed a commit to ameknite/bevy that referenced this pull request Nov 6, 2023
# Objective

Add support for drawing outlines outside the borders of UI nodes.

## Solution
Add a new `Outline` component with `width`, `offset` and `color` fields.
Added `outline_width` and `outline_offset` fields to `Node`. This is set
after layout recomputation by the `resolve_outlines_system`.

Properties of outlines:
* Unlike borders, outlines have to be the same width on each edge.
* Outlines do not occupy any space in the layout.
* The `Outline` component won't be added to any of the UI node bundles,
it needs to be inserted separately.
* Outlines are drawn outside the node's border, so they are clipped
using the clipping rect of their entity's parent UI node (if it exists).
* `Val::Percent` outline widths are resolved based on the width of the
outlined UI node.
* The offset of the `Outline` adds space between an outline and the edge
of its node.

I was leaning towards adding an `outline` field to `Style` but a
separate component seems more efficient for queries and change
detection. The `Outline` component isn't added to bundles for the same
reason.

---
## Examples

* This image is from the `borders` example from the Bevy UI examples but
modified to include outlines. The UI nodes are the dark red rectangles,
the bright red rectangles are borders and the white lines offset from
each node are the outlines. The yellow rectangles are separate nodes
contained with the dark red nodes:
 
<img width="406" alt="outlines"
src="https://github.com/bevyengine/bevy/assets/27962798/4e6f315a-019f-42a4-94ee-cca8e684d64a">

* This is from the same example but using a branch that implements
border-radius. Here the the outlines are in orange and there is no
offset applied. I broke the borders implementation somehow during the
merge, which is why some of the borders from the first screenshot are
missing 😅. The outlines work nicely though (as long as you
can forgive the lack of anti-aliasing):


![image](https://github.com/bevyengine/bevy/assets/27962798/d15560b6-6cd6-42e5-907b-56ccf2ad5e02)

---
## Notes

As I explained above, I don't think the `Outline` component should be
added to UI node bundles. We can have helper functions though, perhaps
something as simple as:

```rust
impl NodeBundle {
    pub fn with_outline(self, outline: Outline) -> (Self, Outline) {
        (self, outline)
    }
}
```

I didn't include anything like this as I wanted to keep the PR's scope
as narrow as possible. Maybe `with_outline` should be in a trait that we
implement for each UI node bundle.

---

## Changelog
Added support for outlines to Bevy UI.
* The `Outline` component adds an outline to a UI node.
* The `outline_width` field added to `Node` holds the resolved width of
the outline, which is set by the `resolve_outlines_system` after layout
recomputation.
* Outlines are drawn by the system `extract_uinode_outlines`.
rdrpenguin04 pushed a commit to rdrpenguin04/bevy that referenced this pull request Jan 9, 2024
# Objective

Add support for drawing outlines outside the borders of UI nodes.

## Solution
Add a new `Outline` component with `width`, `offset` and `color` fields.
Added `outline_width` and `outline_offset` fields to `Node`. This is set
after layout recomputation by the `resolve_outlines_system`.

Properties of outlines:
* Unlike borders, outlines have to be the same width on each edge.
* Outlines do not occupy any space in the layout.
* The `Outline` component won't be added to any of the UI node bundles,
it needs to be inserted separately.
* Outlines are drawn outside the node's border, so they are clipped
using the clipping rect of their entity's parent UI node (if it exists).
* `Val::Percent` outline widths are resolved based on the width of the
outlined UI node.
* The offset of the `Outline` adds space between an outline and the edge
of its node.

I was leaning towards adding an `outline` field to `Style` but a
separate component seems more efficient for queries and change
detection. The `Outline` component isn't added to bundles for the same
reason.

---
## Examples

* This image is from the `borders` example from the Bevy UI examples but
modified to include outlines. The UI nodes are the dark red rectangles,
the bright red rectangles are borders and the white lines offset from
each node are the outlines. The yellow rectangles are separate nodes
contained with the dark red nodes:
 
<img width="406" alt="outlines"
src="https://github.com/bevyengine/bevy/assets/27962798/4e6f315a-019f-42a4-94ee-cca8e684d64a">

* This is from the same example but using a branch that implements
border-radius. Here the the outlines are in orange and there is no
offset applied. I broke the borders implementation somehow during the
merge, which is why some of the borders from the first screenshot are
missing 😅. The outlines work nicely though (as long as you
can forgive the lack of anti-aliasing):


![image](https://github.com/bevyengine/bevy/assets/27962798/d15560b6-6cd6-42e5-907b-56ccf2ad5e02)

---
## Notes

As I explained above, I don't think the `Outline` component should be
added to UI node bundles. We can have helper functions though, perhaps
something as simple as:

```rust
impl NodeBundle {
    pub fn with_outline(self, outline: Outline) -> (Self, Outline) {
        (self, outline)
    }
}
```

I didn't include anything like this as I wanted to keep the PR's scope
as narrow as possible. Maybe `with_outline` should be in a trait that we
implement for each UI node bundle.

---

## Changelog
Added support for outlines to Bevy UI.
* The `Outline` component adds an outline to a UI node.
* The `outline_width` field added to `Node` holds the resolved width of
the outline, which is set by the `resolve_outlines_system` after layout
recomputation.
* Outlines are drawn by the system `extract_uinode_outlines`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants