Skip to content

Commit

Permalink
Add Border and BorderRadius components to bevy_ui
Browse files Browse the repository at this point in the history
  • Loading branch information
oceantume authored and tygyh committed Feb 11, 2024
1 parent d939c44 commit 1f5130f
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 65 deletions.
23 changes: 19 additions & 4 deletions crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
//! This module contains basic node bundles used to build UIs

#[cfg(feature = "bevy_text")]
use crate::widget::TextFlags;
use crate::{
widget::{Button, UiImageSize},
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
UiMaterial, ZIndex,
widget::{Button, TextFlags, UiImageSize},
BackgroundColor, Border, BorderColor, ContentSize, CornerRadius, FocusPolicy, Interaction,
Node, Style, UiImage, UiMaterial, ZIndex,
};
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
Expand Down Expand Up @@ -50,6 +49,10 @@ pub struct NodeBundle {
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Describes the radius of corners for the node
pub corner_radius: CornerRadius,
/// Describes the visual properties of the node's border
pub border: Border,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub view_visibility: ViewVisibility,
/// Indicates the depth at which the node should appear in the UI
Expand All @@ -70,6 +73,8 @@ impl Default for NodeBundle {
visibility: Default::default(),
inherited_visibility: Default::default(),
view_visibility: Default::default(),
corner_radius: Default::default(),
border: Default::default(),
z_index: Default::default(),
}
}
Expand Down Expand Up @@ -115,6 +120,10 @@ pub struct ImageBundle {
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Describes the radius of corners for the node
pub corner_radius: CornerRadius,
/// Describes the visual properties of the node's border
pub border: Border,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub view_visibility: ViewVisibility,
/// Indicates the depth at which the node should appear in the UI
Expand Down Expand Up @@ -330,6 +339,10 @@ pub struct ButtonBundle {
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Describes the radius of corners for the node
pub corner_radius: CornerRadius,
/// Describes the visual properties of the node's border
pub border: Border,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub view_visibility: ViewVisibility,
/// Indicates the depth at which the node should appear in the UI
Expand All @@ -352,6 +365,8 @@ impl Default for ButtonBundle {
visibility: Default::default(),
inherited_visibility: Default::default(),
view_visibility: Default::default(),
corner_radius: Default::default(),
border: Default::default(),
z_index: Default::default(),
}
}
Expand Down
120 changes: 95 additions & 25 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,47 @@ mod pipeline;
mod render_pass;
mod ui_material_pipeline;

use bevy_core_pipeline::core_2d::graph::{Labels2d, SubGraph2d};
use bevy_core_pipeline::core_3d::graph::{Labels3d, SubGraph3d};
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
use bevy_hierarchy::Parent;
use bevy_render::{
render_phase::PhaseItem, render_resource::BindGroupEntries, view::ViewVisibility,
ExtractSchedule, Render,
};
use bevy_sprite::{SpriteAssetEvents, TextureAtlas};
pub use pipeline::*;
pub use render_pass::*;
pub use ui_material_pipeline::*;

use crate::graph::{LabelsUi, SubGraphUi};
use crate::{
texture_slice::ComputedTextureSlices, BackgroundColor, BorderColor, CalculatedClip,
ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiImage, UiScale, Val,
graph::{LabelsUi, SubGraphUi},
texture_slice::ComputedTextureSlices,
BackgroundColor, Border, BorderColor, CalculatedClip, ContentSize, CornerRadius,
DefaultUiCamera, Node, Outline, Style, TargetCamera, UiImage, UiScale, Val,
};

use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle};
use bevy_core_pipeline::{
core_2d::{
graph::{Labels2d, SubGraph2d},
Camera2d,
},
core_3d::{
graph::{Labels3d, SubGraph3d},
Camera3d,
},
};
use bevy_ecs::prelude::*;
use bevy_math::{Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec4Swizzles};
use bevy_hierarchy::Parent;
use bevy_math::{Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles};
use bevy_render::{
camera::Camera,
color::Color,
render_asset::RenderAssets,
render_graph::{RenderGraph, RunGraphOnViewNode},
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, RenderPhase},
render_resource::*,
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, PhaseItem, RenderPhase},
render_resource::{BindGroupEntries, *},
renderer::{RenderDevice, RenderQueue},
texture::Image,
view::{ExtractedView, ViewUniforms},
Extract, RenderApp, RenderSet,
view::{ExtractedView, ViewUniforms, ViewVisibility},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_sprite::{
TextureAtlasLayout, {SpriteAssetEvents, TextureAtlas},
};
use bevy_sprite::TextureAtlasLayout;
#[cfg(feature = "bevy_text")]
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
use bevy_transform::components::GlobalTransform;
Expand Down Expand Up @@ -139,6 +144,9 @@ pub struct ExtractedUiNode {
pub clip: Option<Rect>,
pub flip_x: bool,
pub flip_y: bool,
pub border_color: Option<Color>,
pub border_width: Option<f32>,
pub corner_radius: Option<[f32; 4]>,
// Camera to render this UI node to. By the time it is extracted,
// it is defaulted to a single camera if only one exists.
// Nodes with ambiguous camera will be ignored.
Expand Down Expand Up @@ -277,6 +285,9 @@ pub fn extract_uinode_borders(
flip_x: false,
flip_y: false,
camera_entity,
border_color: Default::default(),
border_width: Default::default(),
corner_radius: Default::default(),
},
);
}
Expand Down Expand Up @@ -368,6 +379,9 @@ pub fn extract_uinode_outlines(
flip_x: false,
flip_y: false,
camera_entity,
border_color: Default::default(),
border_width: Default::default(),
corner_radius: Default::default(),
},
);
}
Expand All @@ -392,6 +406,8 @@ pub fn extract_uinodes(
Option<&TextureAtlas>,
Option<&TargetCamera>,
Option<&ComputedTextureSlices>,
Option<&CornerRadius>,
Option<&Border>,
)>,
>,
) {
Expand All @@ -406,6 +422,8 @@ pub fn extract_uinodes(
atlas,
camera,
slices,
corner_radius,
border,
) in uinode_query.iter()
{
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
Expand All @@ -420,7 +438,16 @@ pub fn extract_uinodes(
if let Some((image, slices)) = maybe_image.zip(slices) {
extracted_uinodes.uinodes.extend(
slices
.extract_ui_nodes(transform, uinode, color, image, clip, camera_entity)
.extract_ui_nodes(
transform,
uinode,
color,
image,
clip,
camera_entity,
*corner_radius.unwrap(),
*border.unwrap(),
)
.map(|e| (commands.spawn_empty().id(), e)),
);
continue;
Expand Down Expand Up @@ -468,6 +495,9 @@ pub fn extract_uinodes(
flip_x,
flip_y,
camera_entity,
border_color: border.map(|border| border.color),
border_width: border.map(|border| border.width),
corner_radius: corner_radius.map(|corner_radius| corner_radius.to_array()),
},
);
}
Expand Down Expand Up @@ -564,11 +594,22 @@ pub fn extract_text_uinodes(
&ViewVisibility,
Option<&CalculatedClip>,
Option<&TargetCamera>,
Option<&CornerRadius>,
Option<&Border>,
)>,
>,
) {
for (uinode, global_transform, text, text_layout_info, view_visibility, clip, camera) in
uinode_query.iter()
for (
uinode,
global_transform,
text,
text_layout_info,
view_visibility,
clip,
camera,
corner_radius,
border,
) in uinode_query.iter()
{
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
else {
Expand Down Expand Up @@ -631,6 +672,9 @@ pub fn extract_text_uinodes(
clip: clip.map(|clip| clip.clip),
flip_x: false,
flip_y: false,
border_color: border.map(|border| border.color),
border_width: border.map(|border| border.width),
corner_radius: corner_radius.map(|corner_radius| corner_radius.to_array()),
camera_entity,
},
);
Expand All @@ -643,6 +687,26 @@ pub fn extract_text_uinodes(
struct UiVertex {
pub position: [f32; 3],
pub uv: [f32; 2],
pub uniform_index: u32,
}

const MAX_UI_UNIFORM_ENTRIES: usize = 256;

#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct UiUniform {
entries: [UiUniformEntry; MAX_UI_UNIFORM_ENTRIES],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct UiUniformEntry {
pub size: Vec2,
pub center: Vec2,
pub border_color: u32,
pub border_width: f32,
/// NOTE: This is a Vec4 because using [f32; 4] with AsStd140 results in a 16-bytes alignment.
pub corner_radius: Vec4,
pub color: [f32; 4],
pub mode: u32,
}
Expand All @@ -651,13 +715,17 @@ struct UiVertex {
pub struct UiMeta {
vertices: BufferVec<UiVertex>,
view_bind_group: Option<BindGroup>,
ui_uniforms: UiUniform,
ui_uniform_bind_group: Option<BindGroup>,
}

impl Default for UiMeta {
fn default() -> Self {
Self {
vertices: BufferVec::new(BufferUsages::VERTEX),
view_bind_group: None,
ui_uniforms: Default::default(),
ui_uniform_bind_group: None,
}
}
}
Expand All @@ -671,10 +739,12 @@ pub(crate) const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [

pub(crate) const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2];

#[derive(Component)]
#[derive(Component, Debug)]
pub struct UiBatch {
pub range: Range<u32>,
pub image: AssetId<Image>,
pub ui_uniform_offset: u32,
pub z: f32,
pub camera: Entity,
}

Expand Down Expand Up @@ -787,6 +857,8 @@ pub fn prepare_uinodes(
range: index..index,
image: extracted_uinode.image,
camera: extracted_uinode.camera_entity,
ui_uniform_offset: 0,
z: 0.0,
};

batches.push((item.entity, new_batch));
Expand Down Expand Up @@ -937,13 +1009,11 @@ pub fn prepare_uinodes(
.map(|pos| pos / atlas_extent)
};

let color = extracted_uinode.color.as_linear_rgba_f32();
for i in QUAD_INDICES {
ui_meta.vertices.push(UiVertex {
position: positions_clipped[i].into(),
uv: uvs[i].into(),
color,
mode,
uniform_index: 0,
});
}
index += QUAD_INDICES.len() as u32;
Expand Down
39 changes: 35 additions & 4 deletions crates/bevy_ui/src/render/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use bevy_render::{
pub struct UiPipeline {
pub view_layout: BindGroupLayout,
pub image_layout: BindGroupLayout,
pub ui_uniform_layout: BindGroupLayout,
}

impl FromWorld for UiPipeline {
Expand All @@ -38,9 +39,25 @@ impl FromWorld for UiPipeline {
),
);

let ui_uniform_layout = render_device.create_bind_group_layout(
Some("ui_uniform_layout"),
&[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(0u64),
},

count: None,
}],
);

UiPipeline {
view_layout,
image_layout,
ui_uniform_layout,
}
}
}
Expand All @@ -57,11 +74,21 @@ impl SpecializedRenderPipeline for UiPipeline {
let vertex_layout = VertexBufferLayout::from_vertex_formats(
VertexStepMode::Vertex,
vec![
// position
// Position
VertexFormat::Float32x3,
// uv
// UV
VertexFormat::Float32x2,
// UiUniform Node Index
VertexFormat::Uint32,
// Size
VertexFormat::Float32x2,
// Center
VertexFormat::Float32x2,
// color
// Border Color
VertexFormat::Uint32,
// Border Width
VertexFormat::Float32,
// Corner Radius
VertexFormat::Float32x4,
// mode
VertexFormat::Uint32,
Expand Down Expand Up @@ -90,7 +117,11 @@ impl SpecializedRenderPipeline for UiPipeline {
write_mask: ColorWrites::ALL,
})],
}),
layout: vec![self.view_layout.clone(), self.image_layout.clone()],
layout: vec![
self.view_layout.clone(),
self.image_layout.clone(),
self.ui_uniform_layout.clone(),
],
push_constant_ranges: Vec::new(),
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
Expand Down
Loading

0 comments on commit 1f5130f

Please sign in to comment.