UI Node Border Radius and Shadows
Objective
Implement border radius and shadows for Bevy UI nodes.
fixes #5924
closely related PRs #7795, #8381, #3991
Solution
Add a component UiBorderRadius which contains a radius value for each corner of the UI node.
The fragment shader uses a signed distance function to generate the rounded corners effect.
Remaining issues and Limitations
~~* UiBorderRadius only takes values in logical pixels. Supporting all the Val variants shouldn't be a problem. Mainly just need to work out sensible rules for how percentage values should be resolved.~~
~~* The border radius value is clamped before being sent to the shader but with very unbalanced opposing borders you can get weird-looking results.~~
- Clipping and Interaction still use unrounded rects.
~~* Anti-aliasing still seems slightly off.~~
Changelog
-
StyleAddedborder_radius: UiBorderRadiusfield. -
UiBorderRadiusNew struct that holds the border radius values. -
extract_uinode_bordersStripped down, most of the work is done in the shader now. Borders are no longer assembled from multiple rects, instead the shader uses a signed distance function to draw the border. -
ExtractedUiNodeAddedborderandcorner_radiusfields. -
UiVertexAddedsize,borderandradiusfields. -
UiPipelineAdded three vertex attributes to the vertex buffer layout, to accept the UI node's size, border thickness and border radius. -
examples Added rounded corners to UI elements in the
button,ui,size_constraintsandbordersexamples.
Migration Guide
Fixed all the obvious bugs. Just percentage and viewport Val support left to implement.
~The inner radii don't look right to me. I think the inner radius should be the outer radius minus the border width. So if you have a 5px border and a corner radius of 5px or less then the inner corner shouldn't be rounded at all.~
The inner radii don't look right to me. I think the inner radius should be the outer radius minus the border width. So if you have a 5px border and a corner radius of 5px or less then the inner corner shouldn't be rounded at all.
That is how I'm calculating the inner radius:
commands.spawn(NodeBundle {
style: Style {
width: Val::Px(400.),
height: Val::Px(400.),
border: UiRect::px(100., 100., 100., 100.),
border_radius: UiBorderRadius::px(50., 100., 150., 200.),
..Default::default()
},
border_color: Color::RED.into(),
background_color: Color::WHITE.into(),
..Default::default()
});
It's because of problems with the anti-aliasing that some of the example images look a little bit off. I'm leaving fixing the AA until last.
That is how I'm calculating the inner radius
Ah, excellent. I think the screenshot in the PR description just doesn't demonstrate this (presumably due to the styles you have picked).
Hope it will be in a 0.11 🙏.
Going to get this finished today, I've worked out how to fix the AA problems and performance issues I think. Just tearing it down atm and testing every edge case. Also going to add at least a basic box-shadow implementation.
Nearly there
Got it, to fix some cases needed to clamp the border radii and not just the internal corner radii:
Need to clamp the shadows too.
Mostly finished, just needs some tidying up, documentation, and refactoring now. The shadow implementation probably needs a few tweaks but it seems okay. Performance isn't great, but it's doing a lot more and it's still much better than 0.10.
Text and Image shadows
They aren't quite right but only spent about 5 minutes on the implementation. Probably won't be included in this PR.
Just dropping in real quick re:shadows, it's very worth considering tailwind's shadows imo. Basic drop shadows look kinda bad by themselves. Mixing some opacity, varied offsets, and multiple shadows is how you get better quality. Can be a follow-up to this PR though, this PR is already doing a lot with both borders + shadows.
https://tailwindcss.com/docs/drop-shadow (see the table at the top for how they map to css)
Toggleable alternate inner radius behaviour, looks cleaner:
Also fixed a bug I found with image nodes, texture atlas images seem to work perfectly now.
Just dropping in real quick re:shadows, it's very worth considering tailwind's shadows imo. Basic drop shadows look kinda bad by themselves. Mixing some opacity, varied offsets, and multiple shadows is how you get better quality. Can be a follow-up to this PR though, this PR is already doing a lot with both borders + shadows.
https://tailwindcss.com/docs/drop-shadow (see the table at the top for how they map to css)
Thanks I'll take a look, the shadows are really basic atm. Probably be a second PR like you say, I don't want to add much more to this, concentrating on improving performance and looking for bugs now.
Yes that's my thinking, almost never need to change border radius whereas changes to border colour on mouse over etc are common, and bypass_change_detection is there for the rare exceptions.
On Fri, 7 Jul 2023, 19:26 Nico Burns, @.***> wrote:
@.**** commented on this pull request.
In crates/bevy_ui/src/ui_node.rs https://github.com/bevyengine/bevy/pull/8973#discussion_r1256271320:
- /// let style = Style {
- /// border_radius: UiBorderRadius {
- /// // The top left corner will be rounded with a radius of 10 logical pixels.
- /// top_left: Val::Px(10.),
- /// // Percentage values are based on the node's smallest dimension, either width or height.
- /// top_right: Val::Percent(20.),
- /// // Viewport coordinates can also be used.
- /// bottom_left: Val::Vw(10.),
- /// // The bottom right corner will be unrounded.
- /// ..Default::default()
- /// },
- /// };
- /// ```
- ///
- /// https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius
- pub border_radius: UiBorderRadius,
BorderColor is a distinct component like BackgroundColor. If colors were stored in Style then changing a color would trigger a layout update because of change detection.
This also applies toBorderRadius, no? Currently changing a border radius will trigger a layout (which it shouldn't). Admittedly, one is less likely to want to change a border radius frequently than a border color.
— Reply to this email directly, view it on GitHub https://github.com/bevyengine/bevy/pull/8973#discussion_r1256271320, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGVK3LW3OJH6UZVRTTZRNB3XPBIDVANCNFSM6AAAAAAZWD47FM . You are receiving this because you authored the thread.Message ID: @.***>
Despite this being very cool and super useful, I think its probably too big to land at this stage in the game for 0.11, especially given that it doesn't have any approvals (and I haven't personally reviewed it yet). I think we should move this to 0.12.
Yeah I really wanted to finish this for 0.11 but I haven't been able to find the time. There are a couple of problems with the current implementation that I need to rework.
I think there might be some weird interactions with UiScale resource. Setting the UiScale.scale to values other than 1 causes the borders to be rendered differently. The images below use border_radius: UiBorderRadius::MAX
scale = 0.5:
scale = 1.0:
scale = 2.0:
Random question - why does the shadow on the light, transparent circle increase the brightness? That's not a shadow :)
It would also be nice to be able to choose the shadow color.
Squircles I propose to add option to use squircle corner rounding. The rounding starts deep within the straight line and gradually grows into a visible curve. Squircles are complex curves that remove the discontinuity between the straight and curved sections of a surface. Look at a curved surface that isn’t a squircle and you’ll be able to see where the straight line ends and the curve starts.
https://en.wikipedia.org/wiki/Squircle#/media/File:Squircle_rounded_square.svg
I just wanted to add that if clipping is holding up this PR, I would say go ahead and commit even if clipping is not there yet, and finish it in a future PR. While round-corner clipping is very handy to have, there are lots of use cases that don't require clipping. And since no one is forced to use rounded corners, it is "strictly no worse" than what they had previously.
The same logic holds for shadows.
Found an article on how to render box shadows well on the GPU https://madebyevan.com/shaders/fast-rounded-rectangle-shadows
https://tchayen.com/thousands-styled-rectangles-in-120fps-on-gpu
@ickshonpe Are you still working on this PR?
Q: Are there any plans to support "inset" shadows? In CSS, I occasionally use this feature for things like text input fields, slider tracks, and other widgets that require a "depressed" look. Implementation-wise, this should just involve drawing the shadow on top of the background (inside the border) and inverting the signed distance calculation.
Also, given that this appears to be stalled, would it make sense to split out the changes to UiRect as a separate PR and commit that now? Those seem relatively uncontroversial.
The creator of this PR has been inactive for a while. Maybe it's time to add the https://github.com/bevyengine/bevy/labels/S-Adopt-Me label?
The creator of this PR has been inactive for a while. Maybe it's time to add the S-Adopt-Me The original PR author has no intent to complete this work. Pick me up! label?
Hi! Are there any other work that needs to be done other than the code style changes?
Oh I see. With #12500 merged, I believe the next work for this PR is to get the shadows in?
Border radius was added in #12500. Shadows should be their own PR.