Skip to content

separate border colors #18682

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

Conversation

robtfm
Copy link
Contributor

@robtfm robtfm commented Apr 2, 2025

Objective

allow specifying the left/top/right/bottom border colors separately for ui elements

fixes #14773

Solution

  • change BorderColor to
pub struct BorderColor {
    pub left: Color,
    pub top: Color,
    pub right: Color,
    pub bottom: Color,
}
  • generate one ui node per distinct border color, set flags for the active borders
  • render only the active borders

i chose to do this rather than adding multiple colors to the ExtractedUiNode in order to minimize the impact for the common case where all border colors are the same.

Testing

modified the borders example to use separate colors:

image

the behaviour is a bit weird but it mirrors html/css border behaviour.


Migration:

To keep the existing behaviour, just change BorderColor(color) into BorderColor::all(color).

@robtfm robtfm added C-Feature A new feature, making something new possible A-UI Graphical user interfaces, styles, layouts, and widgets labels Apr 2, 2025
Copy link
Contributor

@IceSentry IceSentry left a comment

Choose a reason for hiding this comment

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

LGTM (assuming CI is fixed)

@alice-i-cecile alice-i-cecile added A-Rendering Drawing game state to the screen D-Straightforward Simple bug fixes and API improvements, docs, test and examples labels Apr 2, 2025
@alice-i-cecile alice-i-cecile added X-Uncontroversial This work is generally agreed upon S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Apr 2, 2025
@alice-i-cecile alice-i-cecile requested a review from ickshonpe April 2, 2025 17:32
@alice-i-cecile alice-i-cecile added the M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide label Apr 2, 2025
@alice-i-cecile alice-i-cecile added this to the 0.17 milestone Apr 2, 2025
Copy link
Contributor

github-actions bot commented Apr 2, 2025

It looks like your PR is a breaking change, but you didn't provide a migration guide.

Please review the instructions for writing migration guides, then expand or revise the content in the migration guides directory to reflect your changes.

Copy link
Contributor

@ickshonpe ickshonpe left a comment

Choose a reason for hiding this comment

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

I think this is good for an initial implementation.
It's unpleasant how BorderColor stores four colours even though most of the time it will only be using one of them, but other options like having separate components for multi-colored borders or something seem worse.
Drawing the border in multiple passes seems fine. Adding an extra phase item + extracted uinode though for each additional border color is a bit bad, but it doesn't matter for the common single color border case so it should be fine for now.

Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Comment on lines 514 to 554
if let Some(border_color) = maybe_border_color.filter(|bc| !bc.is_fully_transparent()) {
// helper to allow for `Eq` color comparisons so we can build a hashset
let convert =
|c: Color| -> [u8; 16] { bytemuck::cast::<_, [u8; 16]>(c.to_linear()) };

// gather unique colors
unique_colors.clear();
unique_colors.extend(
[
border_color.left,
border_color.top,
border_color.right,
border_color.bottom,
]
.map(convert),
);

for &converted_color in unique_colors.iter() {
let color = bytemuck::cast::<[u8; 16], LinearRgba>(converted_color);

// skip transparent
if color.alpha == 0.0 {
continue;
}

// gather flags of borders that match this color
let border_flags = [
(border_color.left, shader_flags::BORDER_LEFT),
(border_color.top, shader_flags::BORDER_TOP),
(border_color.right, shader_flags::BORDER_RIGHT),
(border_color.bottom, shader_flags::BORDER_BOTTOM),
]
.map(|(border_color, flag)| {
if convert(border_color) == converted_color {
flag
} else {
0
}
})
.into_iter()
.sum::<u32>();
Copy link
Contributor

@ickshonpe ickshonpe Apr 3, 2025

Choose a reason for hiding this comment

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

Thought about it again while trying to go to sleep and this seems pretty simple:

        if let Some(border_color) = maybe_border_color {
                let border_colors = [
                    border_color.left.to_linear(),
                    border_color.top.to_linear(),
                    border_color.right.to_linear(),
                    border_color.bottom.to_linear(),
                ];

                let mut completed_flags = 0;
                for (i, &color) in border_colors.iter().enumerate() {
                    if color.is_fully_transparent() {
                        continue;
                    }

                   let mut border_flags = BORDER_FLAGS[i];

                    if completed_flags & border_flags != 0 {
                        continue;
                    }
                    
                    for j in i + 1..4 {
                        if color == border_colors[j] {
                            border_flags |= BORDER_FLAGS[j];
                        }
                    }
                    completed_flags |= border_flags;

with

const BORDER_FLAGS: [u32; 4] = [
    shader_flags::BORDER_LEFT,
    shader_flags::BORDER_TOP,
    shader_flags::BORDER_RIGHT,
    shader_flags::BORDER_BOTTOM,
];

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes fair enough. has the benefit of predictable ordering too.

Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible D-Straightforward Simple bug fixes and API improvements, docs, test and examples M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Uncontroversial This work is generally agreed upon
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

per edge border colors
4 participants