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 Borders #7795

Merged
merged 34 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
46b7e5e
changes:
ickshonpe Feb 23, 2023
f91e4f3
fix up borders example
ickshonpe Feb 23, 2023
b485e17
changes:
ickshonpe Feb 23, 2023
de7c9d8
lint fix
ickshonpe Feb 23, 2023
caa72fd
improvements to example
ickshonpe Feb 23, 2023
d77ebe9
removed duplicate child insertion from example
ickshonpe Feb 23, 2023
df56157
force inner_min <= inner_max to prevent overlap of opposite edge bord…
ickshonpe Feb 23, 2023
ba63de0
cargo fmt --all
ickshonpe Feb 23, 2023
f3f9a16
move the image handle conversion out of the loop in the extraction fu…
ickshonpe Feb 24, 2023
457648c
Added borders to the buttons in the `many_buttons` stress test example.
ickshonpe Feb 24, 2023
e2b214c
move `compute_matrix()` out of loop
ickshonpe Feb 24, 2023
51e8088
fmt
ickshonpe Feb 24, 2023
6630107
changes:
ickshonpe Mar 1, 2023
0e7fdff
changes:
ickshonpe Mar 3, 2023
d806d33
changes:
ickshonpe Mar 11, 2023
4202bde
Set the default `ButtonBundle` `border_color` to `Color::NONE`
ickshonpe Mar 11, 2023
9eb6262
Merge branch 'main' into borders
ickshonpe Apr 14, 2023
98c88b6
format and build pages
ickshonpe Apr 14, 2023
29e2205
fix for explicit_iter_loop lint
ickshonpe Apr 14, 2023
ac9278f
Fixed `resolve_border_thickness` so its uses the `.max` function not …
ickshonpe Apr 14, 2023
f496b54
Update examples/ui/borders.rs
ickshonpe Apr 18, 2023
e886d7b
Merge branch 'main' into borders
ickshonpe Apr 18, 2023
4f0e38b
changes:
ickshonpe Apr 18, 2023
5dcae37
Merge branch 'main' into borders
ickshonpe May 31, 2023
d045842
Fixed some renamings after merge.
ickshonpe May 31, 2023
1550763
cargo fmt --all
ickshonpe May 31, 2023
ad17940
some minor fixes:
ickshonpe May 31, 2023
f8878cf
Fixed unused imports.
ickshonpe May 31, 2023
e3dcf7c
fixed viewport_debug example
ickshonpe May 31, 2023
af4c565
Added a comment explaining that percentage border values are calculat…
ickshonpe Jun 9, 2023
fbf687e
Replaced logical size calculations with `WindowResolution::width` and…
ickshonpe Jun 9, 2023
7adbf2c
Added `FromReflect` derive, and `reflect(FromReflect, Default)` to `B…
ickshonpe Jun 11, 2023
d2bc8c6
Added a comment explaning the purpose of the translation inside the i…
ickshonpe Jun 11, 2023
b34f363
cargo fmt --all
ickshonpe Jun 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,16 @@ category = "Transforms"
wasm = true

# UI (User Interface)
[[example]]
name = "borders"
path = "examples/ui/borders.rs"

[package.metadata.example.borders]
name = "Borders"
description = "Demonstrates how to create a node with a border"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "button"
path = "examples/ui/button.rs"
Expand Down
100 changes: 100 additions & 0 deletions crates/bevy_ui/src/borders.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use bevy_ecs::prelude::Component;
use bevy_ecs::query::Changed;
use bevy_ecs::query::Or;
use bevy_ecs::query::With;
use bevy_ecs::reflect::ReflectComponent;
use bevy_ecs::system::Query;
use bevy_hierarchy::Children;
use bevy_hierarchy::Parent;
use bevy_math::Rect;
use bevy_math::Vec2;
use bevy_reflect::Reflect;

use crate::Node;
use crate::Style;
use crate::Val;

/// Stores the calculated border geometry
#[derive(Component, Copy, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct CalculatedBorder {
/// The four rects that make up the border
pub edges: [Option<Rect>; 4],
}

impl CalculatedBorder {
const DEFAULT: Self = Self { edges: [None; 4] };
}

impl Default for CalculatedBorder {
fn default() -> Self {
Self::DEFAULT
}
}

fn resolve_thickness(value: Val, parent_width: f32, max_thickness: f32) -> f32 {
match value {
Val::Auto | Val::Undefined => 0.,
Val::Px(px) => px,
Val::Percent(percent) => parent_width * percent / 100.,
}
.min(max_thickness)
}

/// Generates the border geometry
pub fn calculate_borders_system(
parent_query: Query<&Node, With<Children>>,
mut border_query: Query<
(&Node, &Style, &mut CalculatedBorder, Option<&Parent>),
Or<(Changed<Node>, Changed<Style>, Changed<Parent>)>,
>,
) {
for (node, style, mut calculated_border, parent) in border_query.iter_mut() {
let node_size = node.calculated_size;
if node_size.x <= 0. || node_size.y <= 0. {
calculated_border.edges = [None; 4];
continue;
}

let parent_width = parent
.and_then(|parent| parent_query.get(parent.get()).ok())
.map(|parent_node| parent_node.calculated_size.x)
.unwrap_or(0.);
let border = style.border;
let left = resolve_thickness(border.left, parent_width, node_size.x);
let right = resolve_thickness(border.right, parent_width, node_size.x);
let top = resolve_thickness(border.top, parent_width, node_size.y);
let bottom = resolve_thickness(border.bottom, parent_width, node_size.y);
let max = 0.5 * node_size;
let min = -max;
let inner_min = min + Vec2::new(left, top);
let inner_max = max - Vec2::new(right, bottom);

let border_rects = [
Rect {
min,
max: Vec2::new(inner_min.x, max.y),
},
Rect {
min: Vec2::new(inner_max.x, min.y),
max,
},
Rect {
min: Vec2::new(inner_min.x, min.y),
max: Vec2::new(inner_max.x, inner_min.y),
},
Rect {
min: Vec2::new(inner_min.x, inner_max.y),
max: Vec2::new(inner_max.x, max.y),
},
];

for (i, edge) in border_rects.into_iter().enumerate() {
calculated_border.edges[i] = if edge.min.x < edge.max.x && edge.min.y < edge.max.y {
Some(edge)
} else {
None
};
}
}
}
12 changes: 10 additions & 2 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod render;
mod stack;
mod ui_node;

pub mod borders;
pub mod camera_config;
pub mod node_bundles;
pub mod update;
Expand All @@ -25,8 +26,8 @@ pub use ui_node::*;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
camera_config::*, geometry::*, node_bundles::*, ui_node::*, widget::Button, Interaction,
UiScale,
borders::CalculatedBorder, camera_config::*, geometry::*, node_bundles::*, ui_node::*,
widget::Button, Interaction, UiScale,
};
}

Expand Down Expand Up @@ -100,6 +101,8 @@ impl Plugin for UiPlugin {
.register_type::<UiImage>()
.register_type::<Val>()
.register_type::<widget::Button>()
.register_type::<BorderStyle>()
.register_type::<borders::CalculatedBorder>()
.configure_set(UiSystem::Focus.in_base_set(CoreSet::PreUpdate))
.configure_set(UiSystem::Flex.in_base_set(CoreSet::PostUpdate))
.configure_set(UiSystem::Stack.in_base_set(CoreSet::PostUpdate))
Expand Down Expand Up @@ -140,6 +143,11 @@ impl Plugin for UiPlugin {
update_clipping_system
.after(TransformSystem::TransformPropagate)
.in_base_set(CoreSet::PostUpdate),
)
.add_system(
borders::calculate_borders_system
.in_base_set(CoreSet::PostUpdate)
.after(UiSystem::Flex),
);

crate::render::build_ui_render(app);
Expand Down
16 changes: 14 additions & 2 deletions crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! This module contains basic node bundles used to build UIs

use crate::{
widget::Button, BackgroundColor, CalculatedSize, FocusPolicy, Interaction, Node, Style,
UiImage, ZIndex,
borders::CalculatedBorder, widget::Button, BackgroundColor, BorderStyle, CalculatedSize,
FocusPolicy, Interaction, Node, Style, UiImage, ZIndex,
};
use bevy_ecs::bundle::Bundle;
use bevy_render::{
Expand All @@ -23,6 +23,10 @@ pub struct NodeBundle {
pub style: Style,
/// The background color, which serves as a "fill" for this node
pub background_color: BackgroundColor,
/// How to draw the node's border
pub border_style: BorderStyle,
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
/// Stores the border's calculated geometry
pub calculated_border: CalculatedBorder,
/// Whether this node should block interaction with lower nodes
pub focus_policy: FocusPolicy,
/// The transform of the node
Expand All @@ -48,6 +52,8 @@ impl Default for NodeBundle {
NodeBundle {
// Transparent background
background_color: Color::NONE.into(),
border_style: Color::NONE.into(),
calculated_border: Default::default(),
node: Default::default(),
style: Default::default(),
focus_policy: Default::default(),
Expand Down Expand Up @@ -192,6 +198,10 @@ impl TextBundle {
pub struct ButtonBundle {
/// Describes the size of the node
pub node: Node,
/// How to draw the node's border
pub border_style: BorderStyle,
/// Stores the border's calculated geometry
pub calculated_border: CalculatedBorder,
/// Marker component that signals this node is a button
pub button: Button,
/// Describes the style including flexbox settings
Expand Down Expand Up @@ -231,6 +241,8 @@ impl Default for ButtonBundle {
node: Default::default(),
button: Default::default(),
style: Default::default(),
border_style: Default::default(),
calculated_border: Default::default(),
interaction: Default::default(),
background_color: Default::default(),
image: Default::default(),
Expand Down
49 changes: 49 additions & 0 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use bevy_window::{PrimaryWindow, Window};
pub use pipeline::*;
pub use render_pass::*;

use crate::{borders::CalculatedBorder, BorderStyle};
use crate::{prelude::UiCameraConfig, BackgroundColor, CalculatedClip, Node, UiImage, UiStack};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped};
Expand Down Expand Up @@ -78,6 +79,7 @@ pub fn build_ui_render(app: &mut App) {
extract_default_ui_camera_view::<Camera2d>,
extract_default_ui_camera_view::<Camera3d>,
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
extract_uinode_borders.after(RenderUiSystem::ExtractNode),
extract_text_uinodes.after(RenderUiSystem::ExtractNode),
),
)
Expand Down Expand Up @@ -177,6 +179,52 @@ pub struct ExtractedUiNodes {
pub uinodes: Vec<ExtractedUiNode>,
}

pub fn extract_uinode_borders(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
ui_stack: Extract<Res<UiStack>>,
uinode_query: Extract<
Query<(
&GlobalTransform,
&CalculatedBorder,
&BorderStyle,
&ComputedVisibility,
Option<&CalculatedClip>,
)>,
>,
) {
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((transform, border, border_style, visibility, clip)) = uinode_query.get(*entity) {
// Skip invisible nodes
if !visibility.is_visible() || border_style.color.a() == 0.0 {
continue;
}

let image = DEFAULT_IMAGE_HANDLE.typed();

for &border_rect in border.edges.iter().flatten() {
let center = border_rect.center();
let transform = Mat4::from_translation(Vec3::new(center.x, center.y, 0.0))
* transform.compute_matrix();
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved

extracted_uinodes.uinodes.push(ExtractedUiNode {
stack_index,
transform,
color: border_style.color,
rect: Rect {
max: border_rect.size(),
..Default::default()
},
image: image.clone_weak(),
atlas_size: None,
clip: clip.map(|clip| clip.clip),
flip_x: false,
flip_y: false,
});
}
}
}
}

pub fn extract_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
images: Extract<Res<Assets<Image>>>,
Expand All @@ -193,6 +241,7 @@ pub fn extract_uinodes(
>,
) {
extracted_uinodes.uinodes.clear();

for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((uinode, transform, color, maybe_image, visibility, clip)) =
uinode_query.get(*entity)
Expand Down
29 changes: 29 additions & 0 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,35 @@ impl From<Color> for BackgroundColor {
}
}

/// The border color of the node
#[derive(Component, Copy, Clone, Debug, Reflect)]
#[reflect(Component, Default)]
pub struct BorderStyle {
pub color: Color,
}

impl BorderStyle {
pub const DEFAULT: Self = Self {
color: Color::WHITE,
};

pub fn new(color: Color) -> Self {
Self { color }
}
}

impl Default for BorderStyle {
fn default() -> Self {
Self::DEFAULT
}
}

impl From<Color> for BorderStyle {
fn from(color: Color) -> Self {
Self { color }
}
}

/// The 2D texture displayed for this UI node
#[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component, Default)]
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ Example | Description

Example | Description
--- | ---
[Borders](../examples/ui/borders.rs) | Demonstrates how to create a node with a border
[Button](../examples/ui/button.rs) | Illustrates creating and updating a button
[Font Atlas Debug](../examples/ui/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally)
[Relative Cursor Position](../examples/ui/relative_cursor_position.rs) | Showcases the RelativeCursorPosition component
Expand Down
Loading