From 939411e46896dc9b1f1e05bcf678da1f87c064bb Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 26 Sep 2023 14:12:16 +0100 Subject: [PATCH 01/14] Adds 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`. --- crates/bevy_ui/src/layout/mod.rs | 24 ++++++++- crates/bevy_ui/src/lib.rs | 6 +++ crates/bevy_ui/src/render/mod.rs | 93 ++++++++++++++++++++++++++++++++ crates/bevy_ui/src/ui_node.rs | 25 +++++++++ examples/ui/borders.rs | 30 ++++++----- 5 files changed, 164 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 90603577b1d65..22ca82e386337 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -1,9 +1,9 @@ mod convert; pub mod debug; -use crate::{ContentSize, Node, Style, UiScale}; +use crate::{ContentSize, Node, Outline, Style, UiScale}; use bevy_ecs::{ - change_detection::DetectChanges, + change_detection::{DetectChanges, DetectChangesMut}, entity::Entity, event::EventReader, query::{With, Without}, @@ -383,6 +383,26 @@ pub fn ui_layout_system( } } +pub fn resolve_outlines_system( + primary_window: Query<&Window, With>, + ui_scale: Res, + mut outlines_query: Query<(Ref, &mut Node)>, +) { + let viewport_size = primary_window + .get_single() + .map(|window| Vec2::new(window.resolution.width(), window.resolution.height())) + .unwrap_or(Vec2::ZERO) + / ui_scale.0 as f32; + + for (outline, mut node) in outlines_query.iter_mut() { + node.bypass_change_detection().outline_width = outline + .width + .resolve(node.size().x, viewport_size) + .unwrap_or(0.) + .min(0.); + } +} + #[inline] /// Round `value` to the nearest whole integer, with ties (values with a fractional part equal to 0.5) rounded towards positive infinity. fn round_ties_up(value: f32) -> f32 { diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index ae9f9f41885d9..3367a16214e51 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -63,6 +63,8 @@ pub enum UiSystem { Focus, /// After this label, the [`UiStack`] resource has been updated Stack, + /// After this label, node outline widths have been updated + Outlines, } /// The current scale of the UI. @@ -122,6 +124,7 @@ impl Plugin for UiPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .add_systems( PreUpdate, ui_focus_system.in_set(UiSystem::Focus).after(InputSystem), @@ -172,6 +175,9 @@ impl Plugin for UiPlugin { ui_layout_system .in_set(UiSystem::Layout) .before(TransformSystem::TransformPropagate), + resolve_outlines_system + .in_set(UiSystem::Outlines) + .after(UiSystem::Layout), ui_stack_system.in_set(UiSystem::Stack), update_clipping_system.after(TransformSystem::TransformPropagate), ), diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index c4195a6f41200..1651dcea8f99f 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -10,6 +10,7 @@ use bevy_window::{PrimaryWindow, Window}; pub use pipeline::*; pub use render_pass::*; +use crate::Outline; use crate::{ prelude::UiCameraConfig, BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node, Style, UiImage, UiScale, UiStack, UiTextureAtlasImage, Val, @@ -85,6 +86,7 @@ pub fn build_ui_render(app: &mut App) { extract_uinode_borders.after(RenderUiSystem::ExtractAtlasNode), #[cfg(feature = "bevy_text")] extract_text_uinodes.after(RenderUiSystem::ExtractAtlasNode), + extract_uinode_outlines.after(RenderUiSystem::ExtractAtlasNode), ), ) .add_systems( @@ -389,6 +391,97 @@ pub fn extract_uinode_borders( } } +pub fn extract_uinode_outlines( + mut commands: Commands, + mut extracted_uinodes: ResMut, + ui_stack: Extract>, + uinode_query: Extract< + Query< + ( + &Node, + &GlobalTransform, + &Outline, + &ViewVisibility, + Option<&CalculatedClip>, + ), + Without, + >, + >, +) { + let image = AssetId::::default(); + + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { + if let Ok((node, global_transform, outline, view_visibility, clip)) = + uinode_query.get(*entity) + { + // Skip invisible outlines + if !view_visibility.get() || outline.color.a() == 0. || node.outline_width == 0. { + continue; + } + + // Calculate the outline rects. + + let inner_rect = Rect::from_center_size(Vec2::ZERO, node.size()); + let outer_rect = inner_rect.inset(node.outline_width()); + let outline_edges = [ + // Left edge + Rect::new( + outer_rect.min.x, + outer_rect.min.y, + inner_rect.min.x, + outer_rect.max.y, + ), + // Right edge + Rect::new( + inner_rect.max.x, + outer_rect.min.y, + outer_rect.max.x, + outer_rect.max.y, + ), + // Top edge + Rect::new( + inner_rect.min.x, + outer_rect.min.y, + inner_rect.max.x, + inner_rect.min.y, + ), + // Bottom edge + Rect::new( + inner_rect.min.x, + inner_rect.max.y, + inner_rect.max.x, + outer_rect.max.y, + ), + ]; + + let transform = global_transform.compute_matrix(); + + for edge in outline_edges { + if edge.min.x < edge.max.x && edge.min.y < edge.max.y { + extracted_uinodes.uinodes.insert( + commands.spawn_empty().id(), + ExtractedUiNode { + stack_index, + // This translates the uinode's transform to the center of the current border rectangle + transform: transform * Mat4::from_translation(edge.center().extend(0.)), + color: outline.color, + rect: Rect { + max: edge.size(), + ..Default::default() + }, + image, + atlas_size: None, + clip: clip.map(|clip| clip.clip), + flip_x: false, + flip_y: false, + }, + ); + } + } + } + } +} + pub fn extract_uinodes( mut extracted_uinodes: ResMut, images: Extract>>, diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 14080c82228e0..fe2b839941574 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -17,6 +17,10 @@ pub struct Node { /// The size of the node as width and height in logical pixels /// automatically calculated by [`super::layout::ui_layout_system`] pub(crate) calculated_size: Vec2, + /// The width of this node's outline + /// If this value is `Auto`, negative or `0.` then no outline will be rendered + /// automatically calculated by [`super::layout::compute_outlines_system`] + pub(crate) outline_width: f32, } impl Node { @@ -61,11 +65,19 @@ impl Node { ), } } + + #[inline] + /// Returns the thickness of the UI node's outline. + /// If this value is negative or `0.` then no outline will be rendered. + pub fn outline_width(&self) -> f32 { + self.outline_width + } } impl Node { pub const DEFAULT: Self = Self { calculated_size: Vec2::ZERO, + outline_width: 0., }; } @@ -1448,6 +1460,19 @@ impl Default for BorderColor { } } +#[derive(Component, Copy, Clone, Default, Debug, Reflect)] +#[reflect(Component, Default)] +/// The `Outline` component adds an outline outside the border of a UI node. +/// Outlines do not take up space in the layout +pub struct Outline { + /// The width of the outline + /// + /// Percentage `Val` values are resolved based on the width of the outlined [`Node`] + pub width: Val, + /// Color of the outline + pub color: Color, +} + /// The 2D texture displayed for this UI node #[derive(Component, Clone, Debug, Reflect, Default)] #[reflect(Component, Default)] diff --git a/examples/ui/borders.rs b/examples/ui/borders.rs index 9e6cf4e2b3289..08037c2884fba 100644 --- a/examples/ui/borders.rs +++ b/examples/ui/borders.rs @@ -97,20 +97,26 @@ fn setup(mut commands: Commands) { }) .id(); let bordered_node = commands - .spawn(NodeBundle { - style: Style { - width: Val::Px(50.), - height: Val::Px(50.), - border: borders[i % borders.len()], - margin: UiRect::all(Val::Px(2.)), - align_items: AlignItems::Center, - justify_content: JustifyContent::Center, + .spawn(( + NodeBundle { + style: Style { + width: Val::Px(50.), + height: Val::Px(50.), + border: borders[i % borders.len()], + margin: UiRect::all(Val::Px(2.)), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + ..Default::default() + }, + background_color: Color::BLUE.into(), + border_color: Color::WHITE.with_a(0.5).into(), ..Default::default() }, - background_color: Color::BLUE.into(), - border_color: Color::WHITE.with_a(0.5).into(), - ..Default::default() - }) + Outline { + width: Val::Px(1.), + color: Color::PURPLE, + }, + )) .add_child(inner_spot) .id(); commands.entity(root).add_child(bordered_node); From f2a559586bfd02636933b5931bfcd4fbbf7ea84c Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 26 Sep 2023 14:22:03 +0100 Subject: [PATCH 02/14] Use `&Outline` query not `Ref` in `resolve_outlines_system` --- crates/bevy_ui/src/layout/mod.rs | 2 +- crates/bevy_ui/src/ui_node.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 22ca82e386337..dc14d718bd567 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -386,7 +386,7 @@ pub fn ui_layout_system( pub fn resolve_outlines_system( primary_window: Query<&Window, With>, ui_scale: Res, - mut outlines_query: Query<(Ref, &mut Node)>, + mut outlines_query: Query<(&Outline, &mut Node)>, ) { let viewport_size = primary_window .get_single() diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index fe2b839941574..ab94d4e2803fb 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1473,6 +1473,19 @@ pub struct Outline { pub color: Color, } +impl Outline { + /// Create a new outline + pub fn new(width: Val, color: Color) -> Self { + Self { width, color } + } + + /// Create a new outline with width in logical pixels + pub fn px(width: f32, color: Color) -> Self { + Self { width: Val::Px(width), color } + } + +} + /// The 2D texture displayed for this UI node #[derive(Component, Clone, Debug, Reflect, Default)] #[reflect(Component, Default)] From 0e82b9eac1ae1bc4db0b1c6c76072934322ea40b Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 26 Sep 2023 14:23:48 +0100 Subject: [PATCH 03/14] Outline width should be resolved with `max(0)` not `min(0)`. --- crates/bevy_ui/src/layout/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index dc14d718bd567..0aca48cfd2f8a 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -399,7 +399,7 @@ pub fn resolve_outlines_system( .width .resolve(node.size().x, viewport_size) .unwrap_or(0.) - .min(0.); + .max(0.); } } From 53a5e9f10106525d94cbd815d321c79489c626c0 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 26 Sep 2023 14:46:26 +0100 Subject: [PATCH 04/14] Implement clipping for outlines. Outlines are drawn outside out of a node's border, so they are clipped using the clipping Rect of their ui node entity's parent. --- crates/bevy_ui/src/render/mod.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 1651dcea8f99f..df75d154dd943 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -402,16 +402,16 @@ pub fn extract_uinode_outlines( &GlobalTransform, &Outline, &ViewVisibility, - Option<&CalculatedClip>, - ), - Without, + Option<&Parent>, + ), >, >, + clip_query: Query<&CalculatedClip>, ) { let image = AssetId::::default(); for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((node, global_transform, outline, view_visibility, clip)) = + if let Ok((node, global_transform, outline, view_visibility, maybe_parent)) = uinode_query.get(*entity) { // Skip invisible outlines @@ -419,8 +419,10 @@ pub fn extract_uinode_outlines( continue; } - // Calculate the outline rects. + // Outline's are drawn outside of a node's borders, so they are clipped using the clipping Rect of their UI node entity's parent. + let clip = maybe_parent.map(|parent| clip_query.get(parent.get()).ok().map(|clip| clip.clip)).flatten(); + // Calculate the outline rects. let inner_rect = Rect::from_center_size(Vec2::ZERO, node.size()); let outer_rect = inner_rect.inset(node.outline_width()); let outline_edges = [ @@ -471,7 +473,7 @@ pub fn extract_uinode_outlines( }, image, atlas_size: None, - clip: clip.map(|clip| clip.clip), + clip, flip_x: false, flip_y: false, }, From cbdbf85c682ef3d557286880a261159bcba952ae Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 26 Sep 2023 14:57:42 +0100 Subject: [PATCH 05/14] cargo fmt --all --- crates/bevy_ui/src/render/mod.rs | 20 ++++++++++---------- crates/bevy_ui/src/ui_node.rs | 6 ++++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index df75d154dd943..c09f10728de0f 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -396,15 +396,13 @@ pub fn extract_uinode_outlines( mut extracted_uinodes: ResMut, ui_stack: Extract>, uinode_query: Extract< - Query< - ( - &Node, - &GlobalTransform, - &Outline, - &ViewVisibility, - Option<&Parent>, - ), - >, + Query<( + &Node, + &GlobalTransform, + &Outline, + &ViewVisibility, + Option<&Parent>, + )>, >, clip_query: Query<&CalculatedClip>, ) { @@ -420,7 +418,9 @@ pub fn extract_uinode_outlines( } // Outline's are drawn outside of a node's borders, so they are clipped using the clipping Rect of their UI node entity's parent. - let clip = maybe_parent.map(|parent| clip_query.get(parent.get()).ok().map(|clip| clip.clip)).flatten(); + let clip = maybe_parent + .map(|parent| clip_query.get(parent.get()).ok().map(|clip| clip.clip)) + .flatten(); // Calculate the outline rects. let inner_rect = Rect::from_center_size(Vec2::ZERO, node.size()); diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index ab94d4e2803fb..a23201fa7275d 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1481,9 +1481,11 @@ impl Outline { /// Create a new outline with width in logical pixels pub fn px(width: f32, color: Color) -> Self { - Self { width: Val::Px(width), color } + Self { + width: Val::Px(width), + color, + } } - } /// The 2D texture displayed for this UI node From 0d9df1453cc625143b172f19790a502037182984 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 26 Sep 2023 16:31:51 +0100 Subject: [PATCH 06/14] Fixed broken doc comment link --- crates/bevy_ui/src/layout/mod.rs | 1 + crates/bevy_ui/src/ui_node.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 0aca48cfd2f8a..64c8ca6fbdcf8 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -383,6 +383,7 @@ pub fn ui_layout_system( } } +/// Resolve and update the widths of Node outlines pub fn resolve_outlines_system( primary_window: Query<&Window, With>, ui_scale: Res, diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index a23201fa7275d..2d548b02a971f 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -19,7 +19,7 @@ pub struct Node { pub(crate) calculated_size: Vec2, /// The width of this node's outline /// If this value is `Auto`, negative or `0.` then no outline will be rendered - /// automatically calculated by [`super::layout::compute_outlines_system`] + /// automatically calculated by [`super::layout::resolve_outlines_system`] pub(crate) outline_width: f32, } From 677dcda616b6c7c5327dda5dcf4eb107cee85694 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 26 Sep 2023 16:34:05 +0100 Subject: [PATCH 07/14] replaced `map-flatten` with `and_then` in `extract_uinode_outlines` --- crates/bevy_ui/src/render/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index c09f10728de0f..20eb40b0fe4a7 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -419,8 +419,7 @@ pub fn extract_uinode_outlines( // Outline's are drawn outside of a node's borders, so they are clipped using the clipping Rect of their UI node entity's parent. let clip = maybe_parent - .map(|parent| clip_query.get(parent.get()).ok().map(|clip| clip.clip)) - .flatten(); + .and_then(|parent| clip_query.get(parent.get()).ok().map(|clip| clip.clip)); ; // Calculate the outline rects. let inner_rect = Rect::from_center_size(Vec2::ZERO, node.size()); From 472c14ff21c660a0b5fbfe7d6b728dbe1bd9cd7e Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 26 Sep 2023 16:35:42 +0100 Subject: [PATCH 08/14] removed unnecessary semicolon --- crates/bevy_ui/src/render/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 20eb40b0fe4a7..2d27c1f5c3e0f 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -419,7 +419,7 @@ pub fn extract_uinode_outlines( // Outline's are drawn outside of a node's borders, so they are clipped using the clipping Rect of their UI node entity's parent. let clip = maybe_parent - .and_then(|parent| clip_query.get(parent.get()).ok().map(|clip| clip.clip)); ; + .and_then(|parent| clip_query.get(parent.get()).ok().map(|clip| clip.clip)); // Calculate the outline rects. let inner_rect = Rect::from_center_size(Vec2::ZERO, node.size()); From fd639614287daf2c590f6f7e53f4e05624b7c620 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 26 Sep 2023 21:40:14 +0100 Subject: [PATCH 09/14] Allow `Outline`s to be drawn with an offset: * 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 --- crates/bevy_ui/src/layout/mod.rs | 9 ++++++++- crates/bevy_ui/src/render/mod.rs | 3 ++- crates/bevy_ui/src/ui_node.rs | 19 +++++++++++-------- examples/ui/borders.rs | 13 +++++++------ 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 64c8ca6fbdcf8..2b969f3ce7793 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -396,7 +396,14 @@ pub fn resolve_outlines_system( / ui_scale.0 as f32; for (outline, mut node) in outlines_query.iter_mut() { - node.bypass_change_detection().outline_width = outline + let node = node.bypass_change_detection(); + node.outline_width = outline + .width + .resolve(node.size().x, viewport_size) + .unwrap_or(0.) + .max(0.); + + node.outline_offset = outline .width .resolve(node.size().x, viewport_size) .unwrap_or(0.) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 2d27c1f5c3e0f..df27c12ded1cf 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -422,7 +422,8 @@ pub fn extract_uinode_outlines( .and_then(|parent| clip_query.get(parent.get()).ok().map(|clip| clip.clip)); // Calculate the outline rects. - let inner_rect = Rect::from_center_size(Vec2::ZERO, node.size()); + let inner_rect = + Rect::from_center_size(Vec2::ZERO, node.size() + 2. * node.outline_offset); let outer_rect = inner_rect.inset(node.outline_width()); let outline_edges = [ // Left edge diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 2d548b02a971f..9ef6b6ce3b265 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -21,6 +21,8 @@ pub struct Node { /// If this value is `Auto`, negative or `0.` then no outline will be rendered /// automatically calculated by [`super::layout::resolve_outlines_system`] pub(crate) outline_width: f32, + // The amount of space between the outline and the edge of the node + pub(crate) outline_offset: f32, } impl Node { @@ -78,6 +80,7 @@ impl Node { pub const DEFAULT: Self = Self { calculated_size: Vec2::ZERO, outline_width: 0., + outline_offset: 0., }; } @@ -1462,27 +1465,27 @@ impl Default for BorderColor { #[derive(Component, Copy, Clone, Default, Debug, Reflect)] #[reflect(Component, Default)] -/// The `Outline` component adds an outline outside the border of a UI node. +/// The `Outline` component adds an outline outside the edge of a UI node. /// Outlines do not take up space in the layout pub struct Outline { /// The width of the outline /// /// Percentage `Val` values are resolved based on the width of the outlined [`Node`] pub width: Val, + /// The amount of space between a node's outline the edge of the node + /// + /// Percentage `Val` values are resolved based on the width of the outlined [`Node`] + pub offset: Val, /// Color of the outline pub color: Color, } impl Outline { /// Create a new outline - pub fn new(width: Val, color: Color) -> Self { - Self { width, color } - } - - /// Create a new outline with width in logical pixels - pub fn px(width: f32, color: Color) -> Self { + pub const fn new(width: Val, offset: Val, color: Color) -> Self { Self { - width: Val::Px(width), + width, + offset, color, } } diff --git a/examples/ui/borders.rs b/examples/ui/borders.rs index 08037c2884fba..32f43d7c6a0b2 100644 --- a/examples/ui/borders.rs +++ b/examples/ui/borders.rs @@ -23,7 +23,7 @@ fn setup(mut commands: Commands) { align_content: AlignContent::FlexStart, ..Default::default() }, - background_color: BackgroundColor(Color::BLACK), + background_color: BackgroundColor(Color::DARK_GRAY), ..Default::default() }) .id(); @@ -103,18 +103,19 @@ fn setup(mut commands: Commands) { width: Val::Px(50.), height: Val::Px(50.), border: borders[i % borders.len()], - margin: UiRect::all(Val::Px(2.)), + margin: UiRect::all(Val::Px(20.)), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..Default::default() }, - background_color: Color::BLUE.into(), - border_color: Color::WHITE.with_a(0.5).into(), + background_color: Color::MAROON.into(), + border_color: Color::RED.into(), ..Default::default() }, Outline { - width: Val::Px(1.), - color: Color::PURPLE, + width: Val::Px(6.), + offset: Val::Px(6.), + color: Color::WHITE, }, )) .add_child(inner_spot) From a01e2908e1513e61147eda3e101ed3b856f09308 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 2 Oct 2023 10:32:03 +0100 Subject: [PATCH 10/14] added examples to the doc comments for `Outline` --- crates/bevy_ui/src/ui_node.rs | 48 ++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 9ef6b6ce3b265..109f57dc3b8f2 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1465,8 +1465,54 @@ impl Default for BorderColor { #[derive(Component, Copy, Clone, Default, Debug, Reflect)] #[reflect(Component, Default)] -/// The `Outline` component adds an outline outside the edge of a UI node. +/// The [`Outline`] component adds an outline outside the edge of a UI node. /// Outlines do not take up space in the layout +/// +/// To add an [`Outline`] to a ui node you can spawn a `(NodeBundle, Outline)` tuple bundle: +/// ``` +/// # use bevy_ecs::prelude::*; +/// # use bevy_ui::prelude::*; +/// fn setup_ui(mut commands: Commands) { +/// commands.spawn(( +/// NodeBundle { +/// style: Style { +/// width: Val::Px(100.), +/// height: Val::Px(100.), +/// ..Default::default() +/// }, +/// background_color: Color::BLUE.into(), +/// ..Default::default() +/// }, +/// Outline::new(Val::Px(10.), Val::ZERO, Color::RED) +/// )); +/// } +/// ``` +/// +/// [`Outline`] components can also be later to existing UI nodes: +/// ``` +/// # use bevy_ecs::prelude::*; +/// # use bevy_ui::prelude::*; +/// fn outline_hovered_button_system( +/// mut commands: Commands, +/// node_query: Query<(Entity, &Interaction, Option<&mut Outline>), Changed>, +/// ) { +/// for (entity, interaction, mut maybe_outline) in node_query.iter_mut() { +/// let outline_color = +/// if matches(*iteraction, Interaction::Hovered) { +/// Color::WHITE +/// } else { +/// Color::NONE +/// }; +/// if let Some(mut outline) = maybe_outline { +/// outline.color = outline_color; +/// } else { +/// commands.entity(entity).insert(Outline::new(Val::Px(10.), Val::ZERO, outline.color); +/// } +/// } +/// } +/// ``` +/// Adding and removing an [`Outline`] component repeatedly will result in table moves, so it is generally preferable to +/// set `Outline::color` to `Color::NONE` to hide an outline. pub struct Outline { /// The width of the outline /// From 975813bd23f0e20fa77129a4e966e2e5852cfa21 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 2 Oct 2023 10:44:18 +0100 Subject: [PATCH 11/14] edit comments --- crates/bevy_ui/src/ui_node.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 109f57dc3b8f2..beace6605484d 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1488,7 +1488,7 @@ impl Default for BorderColor { /// } /// ``` /// -/// [`Outline`] components can also be later to existing UI nodes: +/// [`Outline`] components can also be added later to existing UI nodes: /// ``` /// # use bevy_ecs::prelude::*; /// # use bevy_ui::prelude::*; @@ -1511,10 +1511,10 @@ impl Default for BorderColor { /// } /// } /// ``` -/// Adding and removing an [`Outline`] component repeatedly will result in table moves, so it is generally preferable to +/// Inserting and removing an [`Outline`] component repeatedly will result in table moves, so it is generally preferable to /// set `Outline::color` to `Color::NONE` to hide an outline. pub struct Outline { - /// The width of the outline + /// The width of the outline. /// /// Percentage `Val` values are resolved based on the width of the outlined [`Node`] pub width: Val, @@ -1523,6 +1523,9 @@ pub struct Outline { /// Percentage `Val` values are resolved based on the width of the outlined [`Node`] pub offset: Val, /// Color of the outline + /// + /// If you are frequently toggling outlines for a UI node on and off it is recommended to set `Color::None` to hide the outline. + /// This avoids the table moves that would occcur from the repeated insertion and removal of the `Outline` component. pub color: Color, } From 39b390eec9eab1229a8eb5500685204a06d248b3 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 3 Oct 2023 12:26:33 +0100 Subject: [PATCH 12/14] fixed doc comment example errors --- crates/bevy_ui/src/ui_node.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 2a4167e9effae..866144c4b762c 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1482,6 +1482,7 @@ impl Default for BorderColor { /// ``` /// # use bevy_ecs::prelude::*; /// # use bevy_ui::prelude::*; +/// # use bevy_render::prelude::Color; /// fn setup_ui(mut commands: Commands) { /// commands.spawn(( /// NodeBundle { @@ -1516,7 +1517,7 @@ impl Default for BorderColor { /// if let Some(mut outline) = maybe_outline { /// outline.color = outline_color; /// } else { -/// commands.entity(entity).insert(Outline::new(Val::Px(10.), Val::ZERO, outline.color); +/// commands.entity(entity).insert(Outline::new(Val::Px(10.), Val::ZERO, outline.color)); /// } /// } /// } From 8cd8e558863893deba0ab47a463158c40d4301af Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 3 Oct 2023 13:03:13 +0100 Subject: [PATCH 13/14] fixed doc comments --- crates/bevy_ui/src/ui_node.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 866144c4b762c..2a05badddba99 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1503,13 +1503,14 @@ impl Default for BorderColor { /// ``` /// # use bevy_ecs::prelude::*; /// # use bevy_ui::prelude::*; +/// # use bevy_render::prelude::Color; /// fn outline_hovered_button_system( /// mut commands: Commands, /// node_query: Query<(Entity, &Interaction, Option<&mut Outline>), Changed>, /// ) { /// for (entity, interaction, mut maybe_outline) in node_query.iter_mut() { /// let outline_color = -/// if matches(*iteraction, Interaction::Hovered) { +/// if matches!(*interaction, Interaction::Hovered) { /// Color::WHITE /// } else { /// Color::NONE @@ -1517,7 +1518,7 @@ impl Default for BorderColor { /// if let Some(mut outline) = maybe_outline { /// outline.color = outline_color; /// } else { -/// commands.entity(entity).insert(Outline::new(Val::Px(10.), Val::ZERO, outline.color)); +/// commands.entity(entity).insert(Outline::new(Val::Px(10.), Val::ZERO, outline_color)); /// } /// } /// } From 32c6c098b353b6548e826a75a71a531fa0f456b5 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 3 Oct 2023 13:48:33 +0100 Subject: [PATCH 14/14] comments fixed again --- crates/bevy_ui/src/ui_node.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 2a05badddba99..7b6209c685830 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1506,7 +1506,7 @@ impl Default for BorderColor { /// # use bevy_render::prelude::Color; /// fn outline_hovered_button_system( /// mut commands: Commands, -/// node_query: Query<(Entity, &Interaction, Option<&mut Outline>), Changed>, +/// mut node_query: Query<(Entity, &Interaction, Option<&mut Outline>), Changed>, /// ) { /// for (entity, interaction, mut maybe_outline) in node_query.iter_mut() { /// let outline_color =