Skip to content

Commit 1f5130f

Browse files
oceantumetygyh
authored andcommitted
Add Border and BorderRadius components to bevy_ui
1 parent d939c44 commit 1f5130f

File tree

9 files changed

+314
-65
lines changed

9 files changed

+314
-65
lines changed

crates/bevy_ui/src/node_bundles.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
//! This module contains basic node bundles used to build UIs
22
33
#[cfg(feature = "bevy_text")]
4-
use crate::widget::TextFlags;
54
use crate::{
6-
widget::{Button, UiImageSize},
7-
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
8-
UiMaterial, ZIndex,
5+
widget::{Button, TextFlags, UiImageSize},
6+
BackgroundColor, Border, BorderColor, ContentSize, CornerRadius, FocusPolicy, Interaction,
7+
Node, Style, UiImage, UiMaterial, ZIndex,
98
};
109
use bevy_asset::Handle;
1110
use bevy_ecs::bundle::Bundle;
@@ -50,6 +49,10 @@ pub struct NodeBundle {
5049
pub visibility: Visibility,
5150
/// Inherited visibility of an entity.
5251
pub inherited_visibility: InheritedVisibility,
52+
/// Describes the radius of corners for the node
53+
pub corner_radius: CornerRadius,
54+
/// Describes the visual properties of the node's border
55+
pub border: Border,
5356
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
5457
pub view_visibility: ViewVisibility,
5558
/// Indicates the depth at which the node should appear in the UI
@@ -70,6 +73,8 @@ impl Default for NodeBundle {
7073
visibility: Default::default(),
7174
inherited_visibility: Default::default(),
7275
view_visibility: Default::default(),
76+
corner_radius: Default::default(),
77+
border: Default::default(),
7378
z_index: Default::default(),
7479
}
7580
}
@@ -115,6 +120,10 @@ pub struct ImageBundle {
115120
pub visibility: Visibility,
116121
/// Inherited visibility of an entity.
117122
pub inherited_visibility: InheritedVisibility,
123+
/// Describes the radius of corners for the node
124+
pub corner_radius: CornerRadius,
125+
/// Describes the visual properties of the node's border
126+
pub border: Border,
118127
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
119128
pub view_visibility: ViewVisibility,
120129
/// Indicates the depth at which the node should appear in the UI
@@ -330,6 +339,10 @@ pub struct ButtonBundle {
330339
pub visibility: Visibility,
331340
/// Inherited visibility of an entity.
332341
pub inherited_visibility: InheritedVisibility,
342+
/// Describes the radius of corners for the node
343+
pub corner_radius: CornerRadius,
344+
/// Describes the visual properties of the node's border
345+
pub border: Border,
333346
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
334347
pub view_visibility: ViewVisibility,
335348
/// Indicates the depth at which the node should appear in the UI
@@ -352,6 +365,8 @@ impl Default for ButtonBundle {
352365
visibility: Default::default(),
353366
inherited_visibility: Default::default(),
354367
view_visibility: Default::default(),
368+
corner_radius: Default::default(),
369+
border: Default::default(),
355370
z_index: Default::default(),
356371
}
357372
}

crates/bevy_ui/src/render/mod.rs

Lines changed: 95 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,47 @@ mod pipeline;
22
mod render_pass;
33
mod ui_material_pipeline;
44

5-
use bevy_core_pipeline::core_2d::graph::{Labels2d, SubGraph2d};
6-
use bevy_core_pipeline::core_3d::graph::{Labels3d, SubGraph3d};
7-
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
8-
use bevy_hierarchy::Parent;
9-
use bevy_render::{
10-
render_phase::PhaseItem, render_resource::BindGroupEntries, view::ViewVisibility,
11-
ExtractSchedule, Render,
12-
};
13-
use bevy_sprite::{SpriteAssetEvents, TextureAtlas};
145
pub use pipeline::*;
156
pub use render_pass::*;
167
pub use ui_material_pipeline::*;
178

18-
use crate::graph::{LabelsUi, SubGraphUi};
199
use crate::{
20-
texture_slice::ComputedTextureSlices, BackgroundColor, BorderColor, CalculatedClip,
21-
ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiImage, UiScale, Val,
10+
graph::{LabelsUi, SubGraphUi},
11+
texture_slice::ComputedTextureSlices,
12+
BackgroundColor, Border, BorderColor, CalculatedClip, ContentSize, CornerRadius,
13+
DefaultUiCamera, Node, Outline, Style, TargetCamera, UiImage, UiScale, Val,
2214
};
2315

2416
use bevy_app::prelude::*;
2517
use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle};
18+
use bevy_core_pipeline::{
19+
core_2d::{
20+
graph::{Labels2d, SubGraph2d},
21+
Camera2d,
22+
},
23+
core_3d::{
24+
graph::{Labels3d, SubGraph3d},
25+
Camera3d,
26+
},
27+
};
2628
use bevy_ecs::prelude::*;
27-
use bevy_math::{Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec4Swizzles};
29+
use bevy_hierarchy::Parent;
30+
use bevy_math::{Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles};
2831
use bevy_render::{
2932
camera::Camera,
3033
color::Color,
3134
render_asset::RenderAssets,
3235
render_graph::{RenderGraph, RunGraphOnViewNode},
33-
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, RenderPhase},
34-
render_resource::*,
36+
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, PhaseItem, RenderPhase},
37+
render_resource::{BindGroupEntries, *},
3538
renderer::{RenderDevice, RenderQueue},
3639
texture::Image,
37-
view::{ExtractedView, ViewUniforms},
38-
Extract, RenderApp, RenderSet,
40+
view::{ExtractedView, ViewUniforms, ViewVisibility},
41+
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
42+
};
43+
use bevy_sprite::{
44+
TextureAtlasLayout, {SpriteAssetEvents, TextureAtlas},
3945
};
40-
use bevy_sprite::TextureAtlasLayout;
4146
#[cfg(feature = "bevy_text")]
4247
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
4348
use bevy_transform::components::GlobalTransform;
@@ -139,6 +144,9 @@ pub struct ExtractedUiNode {
139144
pub clip: Option<Rect>,
140145
pub flip_x: bool,
141146
pub flip_y: bool,
147+
pub border_color: Option<Color>,
148+
pub border_width: Option<f32>,
149+
pub corner_radius: Option<[f32; 4]>,
142150
// Camera to render this UI node to. By the time it is extracted,
143151
// it is defaulted to a single camera if only one exists.
144152
// Nodes with ambiguous camera will be ignored.
@@ -277,6 +285,9 @@ pub fn extract_uinode_borders(
277285
flip_x: false,
278286
flip_y: false,
279287
camera_entity,
288+
border_color: Default::default(),
289+
border_width: Default::default(),
290+
corner_radius: Default::default(),
280291
},
281292
);
282293
}
@@ -368,6 +379,9 @@ pub fn extract_uinode_outlines(
368379
flip_x: false,
369380
flip_y: false,
370381
camera_entity,
382+
border_color: Default::default(),
383+
border_width: Default::default(),
384+
corner_radius: Default::default(),
371385
},
372386
);
373387
}
@@ -392,6 +406,8 @@ pub fn extract_uinodes(
392406
Option<&TextureAtlas>,
393407
Option<&TargetCamera>,
394408
Option<&ComputedTextureSlices>,
409+
Option<&CornerRadius>,
410+
Option<&Border>,
395411
)>,
396412
>,
397413
) {
@@ -406,6 +422,8 @@ pub fn extract_uinodes(
406422
atlas,
407423
camera,
408424
slices,
425+
corner_radius,
426+
border,
409427
) in uinode_query.iter()
410428
{
411429
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
@@ -420,7 +438,16 @@ pub fn extract_uinodes(
420438
if let Some((image, slices)) = maybe_image.zip(slices) {
421439
extracted_uinodes.uinodes.extend(
422440
slices
423-
.extract_ui_nodes(transform, uinode, color, image, clip, camera_entity)
441+
.extract_ui_nodes(
442+
transform,
443+
uinode,
444+
color,
445+
image,
446+
clip,
447+
camera_entity,
448+
*corner_radius.unwrap(),
449+
*border.unwrap(),
450+
)
424451
.map(|e| (commands.spawn_empty().id(), e)),
425452
);
426453
continue;
@@ -468,6 +495,9 @@ pub fn extract_uinodes(
468495
flip_x,
469496
flip_y,
470497
camera_entity,
498+
border_color: border.map(|border| border.color),
499+
border_width: border.map(|border| border.width),
500+
corner_radius: corner_radius.map(|corner_radius| corner_radius.to_array()),
471501
},
472502
);
473503
}
@@ -564,11 +594,22 @@ pub fn extract_text_uinodes(
564594
&ViewVisibility,
565595
Option<&CalculatedClip>,
566596
Option<&TargetCamera>,
597+
Option<&CornerRadius>,
598+
Option<&Border>,
567599
)>,
568600
>,
569601
) {
570-
for (uinode, global_transform, text, text_layout_info, view_visibility, clip, camera) in
571-
uinode_query.iter()
602+
for (
603+
uinode,
604+
global_transform,
605+
text,
606+
text_layout_info,
607+
view_visibility,
608+
clip,
609+
camera,
610+
corner_radius,
611+
border,
612+
) in uinode_query.iter()
572613
{
573614
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
574615
else {
@@ -631,6 +672,9 @@ pub fn extract_text_uinodes(
631672
clip: clip.map(|clip| clip.clip),
632673
flip_x: false,
633674
flip_y: false,
675+
border_color: border.map(|border| border.color),
676+
border_width: border.map(|border| border.width),
677+
corner_radius: corner_radius.map(|corner_radius| corner_radius.to_array()),
634678
camera_entity,
635679
},
636680
);
@@ -643,6 +687,26 @@ pub fn extract_text_uinodes(
643687
struct UiVertex {
644688
pub position: [f32; 3],
645689
pub uv: [f32; 2],
690+
pub uniform_index: u32,
691+
}
692+
693+
const MAX_UI_UNIFORM_ENTRIES: usize = 256;
694+
695+
#[repr(C)]
696+
#[derive(Copy, Clone, Debug)]
697+
pub struct UiUniform {
698+
entries: [UiUniformEntry; MAX_UI_UNIFORM_ENTRIES],
699+
}
700+
701+
#[repr(C)]
702+
#[derive(Copy, Clone, Debug, Default)]
703+
pub struct UiUniformEntry {
704+
pub size: Vec2,
705+
pub center: Vec2,
706+
pub border_color: u32,
707+
pub border_width: f32,
708+
/// NOTE: This is a Vec4 because using [f32; 4] with AsStd140 results in a 16-bytes alignment.
709+
pub corner_radius: Vec4,
646710
pub color: [f32; 4],
647711
pub mode: u32,
648712
}
@@ -651,13 +715,17 @@ struct UiVertex {
651715
pub struct UiMeta {
652716
vertices: BufferVec<UiVertex>,
653717
view_bind_group: Option<BindGroup>,
718+
ui_uniforms: UiUniform,
719+
ui_uniform_bind_group: Option<BindGroup>,
654720
}
655721

656722
impl Default for UiMeta {
657723
fn default() -> Self {
658724
Self {
659725
vertices: BufferVec::new(BufferUsages::VERTEX),
660726
view_bind_group: None,
727+
ui_uniforms: Default::default(),
728+
ui_uniform_bind_group: None,
661729
}
662730
}
663731
}
@@ -671,10 +739,12 @@ pub(crate) const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [
671739

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

674-
#[derive(Component)]
742+
#[derive(Component, Debug)]
675743
pub struct UiBatch {
676744
pub range: Range<u32>,
677745
pub image: AssetId<Image>,
746+
pub ui_uniform_offset: u32,
747+
pub z: f32,
678748
pub camera: Entity,
679749
}
680750

@@ -787,6 +857,8 @@ pub fn prepare_uinodes(
787857
range: index..index,
788858
image: extracted_uinode.image,
789859
camera: extracted_uinode.camera_entity,
860+
ui_uniform_offset: 0,
861+
z: 0.0,
790862
};
791863

792864
batches.push((item.entity, new_batch));
@@ -937,13 +1009,11 @@ pub fn prepare_uinodes(
9371009
.map(|pos| pos / atlas_extent)
9381010
};
9391011

940-
let color = extracted_uinode.color.as_linear_rgba_f32();
9411012
for i in QUAD_INDICES {
9421013
ui_meta.vertices.push(UiVertex {
9431014
position: positions_clipped[i].into(),
9441015
uv: uvs[i].into(),
945-
color,
946-
mode,
1016+
uniform_index: 0,
9471017
});
9481018
}
9491019
index += QUAD_INDICES.len() as u32;

crates/bevy_ui/src/render/pipeline.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use bevy_render::{
1313
pub struct UiPipeline {
1414
pub view_layout: BindGroupLayout,
1515
pub image_layout: BindGroupLayout,
16+
pub ui_uniform_layout: BindGroupLayout,
1617
}
1718

1819
impl FromWorld for UiPipeline {
@@ -38,9 +39,25 @@ impl FromWorld for UiPipeline {
3839
),
3940
);
4041

42+
let ui_uniform_layout = render_device.create_bind_group_layout(
43+
Some("ui_uniform_layout"),
44+
&[BindGroupLayoutEntry {
45+
binding: 0,
46+
visibility: ShaderStages::VERTEX,
47+
ty: BindingType::Buffer {
48+
ty: BufferBindingType::Uniform,
49+
has_dynamic_offset: true,
50+
min_binding_size: BufferSize::new(0u64),
51+
},
52+
53+
count: None,
54+
}],
55+
);
56+
4157
UiPipeline {
4258
view_layout,
4359
image_layout,
60+
ui_uniform_layout,
4461
}
4562
}
4663
}
@@ -57,11 +74,21 @@ impl SpecializedRenderPipeline for UiPipeline {
5774
let vertex_layout = VertexBufferLayout::from_vertex_formats(
5875
VertexStepMode::Vertex,
5976
vec![
60-
// position
77+
// Position
6178
VertexFormat::Float32x3,
62-
// uv
79+
// UV
80+
VertexFormat::Float32x2,
81+
// UiUniform Node Index
82+
VertexFormat::Uint32,
83+
// Size
84+
VertexFormat::Float32x2,
85+
// Center
6386
VertexFormat::Float32x2,
64-
// color
87+
// Border Color
88+
VertexFormat::Uint32,
89+
// Border Width
90+
VertexFormat::Float32,
91+
// Corner Radius
6592
VertexFormat::Float32x4,
6693
// mode
6794
VertexFormat::Uint32,
@@ -90,7 +117,11 @@ impl SpecializedRenderPipeline for UiPipeline {
90117
write_mask: ColorWrites::ALL,
91118
})],
92119
}),
93-
layout: vec![self.view_layout.clone(), self.image_layout.clone()],
120+
layout: vec![
121+
self.view_layout.clone(),
122+
self.image_layout.clone(),
123+
self.ui_uniform_layout.clone(),
124+
],
94125
push_constant_ranges: Vec::new(),
95126
primitive: PrimitiveState {
96127
front_face: FrontFace::Ccw,

0 commit comments

Comments
 (0)