mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Add CSS Grid support to bevy_ui
(#8026)
# Objective An easy way to create 2D grid layouts ## Solution Enable the `grid` feature in Taffy and add new style types for defining grids. ## Notes - ~I'm having a bit of trouble getting `#[derive(Reflect)]` to work properly. Help with that would be appreciated (EDIT: got it to compile by ignoring the problematic fields, but this presumably can't be merged).~ This is now fixed - ~The alignment types now have a `Normal` variant because I couldn't get reflect to work with `Option`.~ I've decided to stick with the flattened variant, as it saves a level of wrapping when authoring styles. But I've renamed the variants from `Normal` to `Default`. - ~This currently exposes a simplified API on top of grid. In particular the following is not currently supported:~ - ~Negative grid indices~ Now supported. - ~Custom `end` values for grid placement (you can only use `start` and `span`)~ Now supported - ~`minmax()` track sizing functions~ minmax is now support through a `GridTrack::minmax()` constructor - ~`repeat()`~ repeat is now implemented as `RepeatedGridTrack` - ~Documentation still needs to be improved.~ An initial pass over the documentation has been completed. ## Screenshot <img width="846" alt="Screenshot 2023-03-10 at 17 56 21" src="https://user-images.githubusercontent.com/1007307/224435332-69aa9eac-123d-4856-b75d-5449d3f1d426.png"> --- ## Changelog - Support for CSS Grid layout added to `bevy_ui` --------- Co-authored-by: Rob Parrett <robparrett@gmail.com> Co-authored-by: Andreas Weibye <13300393+Weibye@users.noreply.github.com>
This commit is contained in:
parent
cfa750a741
commit
363d0f0c7c
9 changed files with 1415 additions and 171 deletions
12
Cargo.toml
12
Cargo.toml
|
@ -1806,7 +1806,17 @@ path = "examples/ui/flex_layout.rs"
|
|||
name = "Flex Layout"
|
||||
description = "Demonstrates how the AlignItems and JustifyContent properties can be composed to layout nodes and position text"
|
||||
category = "UI (User Interface)"
|
||||
wasm = false
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "grid"
|
||||
path = "examples/ui/grid.rs"
|
||||
|
||||
[package.metadata.example.grid]
|
||||
name = "CSS Grid"
|
||||
description = "An example for CSS Grid layout"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "transparency_ui"
|
||||
|
|
|
@ -31,7 +31,7 @@ bevy_window = { path = "../bevy_window", version = "0.11.0-dev" }
|
|||
bevy_utils = { path = "../bevy_utils", version = "0.11.0-dev" }
|
||||
|
||||
# other
|
||||
taffy = { version = "0.3.10", default-features = false, features = ["std"] }
|
||||
taffy = { version = "0.3.10" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
smallvec = { version = "1.6", features = ["union", "const_generics"] }
|
||||
bytemuck = { version = "1.5", features = ["derive"] }
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
use taffy::style_helpers;
|
||||
|
||||
use crate::{
|
||||
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
|
||||
PositionType, Size, Style, UiRect, Val,
|
||||
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, GridAutoFlow,
|
||||
GridPlacement, GridTrack, GridTrackRepetition, JustifyContent, JustifyItems, JustifySelf,
|
||||
MaxTrackSizingFunction, MinTrackSizingFunction, PositionType, RepeatedGridTrack, Size, Style,
|
||||
UiRect, Val,
|
||||
};
|
||||
|
||||
use super::LayoutContext;
|
||||
|
@ -74,10 +78,12 @@ pub fn from_style(context: &LayoutContext, style: &Style) -> taffy::style::Style
|
|||
position: style.position_type.into(),
|
||||
flex_direction: style.flex_direction.into(),
|
||||
flex_wrap: style.flex_wrap.into(),
|
||||
align_items: Some(style.align_items.into()),
|
||||
align_items: style.align_items.into(),
|
||||
justify_items: style.justify_items.into(),
|
||||
align_self: style.align_self.into(),
|
||||
align_content: Some(style.align_content.into()),
|
||||
justify_content: Some(style.justify_content.into()),
|
||||
justify_self: style.justify_self.into(),
|
||||
align_content: style.align_content.into(),
|
||||
justify_content: style.justify_content.into(),
|
||||
inset: taffy::prelude::Rect {
|
||||
left: style.left.into_length_percentage_auto(context),
|
||||
right: style.right.into_length_percentage_auto(context),
|
||||
|
@ -107,20 +113,56 @@ pub fn from_style(context: &LayoutContext, style: &Style) -> taffy::style::Style
|
|||
gap: style
|
||||
.gap
|
||||
.map_to_taffy_size(|s| s.into_length_percentage(context)),
|
||||
justify_self: None,
|
||||
grid_auto_flow: style.grid_auto_flow.into(),
|
||||
grid_template_rows: style
|
||||
.grid_template_rows
|
||||
.iter()
|
||||
.map(|track| track.clone_into_repeated_taffy_track(context))
|
||||
.collect::<Vec<_>>(),
|
||||
grid_template_columns: style
|
||||
.grid_template_columns
|
||||
.iter()
|
||||
.map(|track| track.clone_into_repeated_taffy_track(context))
|
||||
.collect::<Vec<_>>(),
|
||||
grid_auto_rows: style
|
||||
.grid_auto_rows
|
||||
.iter()
|
||||
.map(|track| track.into_taffy_track(context))
|
||||
.collect::<Vec<_>>(),
|
||||
grid_auto_columns: style
|
||||
.grid_auto_columns
|
||||
.iter()
|
||||
.map(|track| track.into_taffy_track(context))
|
||||
.collect::<Vec<_>>(),
|
||||
grid_row: style.grid_row.into(),
|
||||
grid_column: style.grid_column.into(),
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AlignItems> for taffy::style::AlignItems {
|
||||
impl From<AlignItems> for Option<taffy::style::AlignItems> {
|
||||
fn from(value: AlignItems) -> Self {
|
||||
match value {
|
||||
AlignItems::Start => taffy::style::AlignItems::Start,
|
||||
AlignItems::End => taffy::style::AlignItems::End,
|
||||
AlignItems::FlexStart => taffy::style::AlignItems::FlexStart,
|
||||
AlignItems::FlexEnd => taffy::style::AlignItems::FlexEnd,
|
||||
AlignItems::Center => taffy::style::AlignItems::Center,
|
||||
AlignItems::Baseline => taffy::style::AlignItems::Baseline,
|
||||
AlignItems::Stretch => taffy::style::AlignItems::Stretch,
|
||||
AlignItems::Default => None,
|
||||
AlignItems::Start => taffy::style::AlignItems::Start.into(),
|
||||
AlignItems::End => taffy::style::AlignItems::End.into(),
|
||||
AlignItems::FlexStart => taffy::style::AlignItems::FlexStart.into(),
|
||||
AlignItems::FlexEnd => taffy::style::AlignItems::FlexEnd.into(),
|
||||
AlignItems::Center => taffy::style::AlignItems::Center.into(),
|
||||
AlignItems::Baseline => taffy::style::AlignItems::Baseline.into(),
|
||||
AlignItems::Stretch => taffy::style::AlignItems::Stretch.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JustifyItems> for Option<taffy::style::JustifyItems> {
|
||||
fn from(value: JustifyItems) -> Self {
|
||||
match value {
|
||||
JustifyItems::Default => None,
|
||||
JustifyItems::Start => taffy::style::JustifyItems::Start.into(),
|
||||
JustifyItems::End => taffy::style::JustifyItems::End.into(),
|
||||
JustifyItems::Center => taffy::style::JustifyItems::Center.into(),
|
||||
JustifyItems::Baseline => taffy::style::JustifyItems::Baseline.into(),
|
||||
JustifyItems::Stretch => taffy::style::JustifyItems::Stretch.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,18 +182,48 @@ impl From<AlignSelf> for Option<taffy::style::AlignSelf> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<AlignContent> for taffy::style::AlignContent {
|
||||
impl From<JustifySelf> for Option<taffy::style::JustifySelf> {
|
||||
fn from(value: JustifySelf) -> Self {
|
||||
match value {
|
||||
JustifySelf::Auto => None,
|
||||
JustifySelf::Start => taffy::style::JustifySelf::Start.into(),
|
||||
JustifySelf::End => taffy::style::JustifySelf::End.into(),
|
||||
JustifySelf::Center => taffy::style::JustifySelf::Center.into(),
|
||||
JustifySelf::Baseline => taffy::style::JustifySelf::Baseline.into(),
|
||||
JustifySelf::Stretch => taffy::style::JustifySelf::Stretch.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AlignContent> for Option<taffy::style::AlignContent> {
|
||||
fn from(value: AlignContent) -> Self {
|
||||
match value {
|
||||
AlignContent::Start => taffy::style::AlignContent::Start,
|
||||
AlignContent::End => taffy::style::AlignContent::End,
|
||||
AlignContent::FlexStart => taffy::style::AlignContent::FlexStart,
|
||||
AlignContent::FlexEnd => taffy::style::AlignContent::FlexEnd,
|
||||
AlignContent::Center => taffy::style::AlignContent::Center,
|
||||
AlignContent::Stretch => taffy::style::AlignContent::Stretch,
|
||||
AlignContent::SpaceBetween => taffy::style::AlignContent::SpaceBetween,
|
||||
AlignContent::SpaceAround => taffy::style::AlignContent::SpaceAround,
|
||||
AlignContent::SpaceEvenly => taffy::style::AlignContent::SpaceEvenly,
|
||||
AlignContent::Default => None,
|
||||
AlignContent::Start => taffy::style::AlignContent::Start.into(),
|
||||
AlignContent::End => taffy::style::AlignContent::End.into(),
|
||||
AlignContent::FlexStart => taffy::style::AlignContent::FlexStart.into(),
|
||||
AlignContent::FlexEnd => taffy::style::AlignContent::FlexEnd.into(),
|
||||
AlignContent::Center => taffy::style::AlignContent::Center.into(),
|
||||
AlignContent::Stretch => taffy::style::AlignContent::Stretch.into(),
|
||||
AlignContent::SpaceBetween => taffy::style::AlignContent::SpaceBetween.into(),
|
||||
AlignContent::SpaceAround => taffy::style::AlignContent::SpaceAround.into(),
|
||||
AlignContent::SpaceEvenly => taffy::style::AlignContent::SpaceEvenly.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JustifyContent> for Option<taffy::style::JustifyContent> {
|
||||
fn from(value: JustifyContent) -> Self {
|
||||
match value {
|
||||
JustifyContent::Default => None,
|
||||
JustifyContent::Start => taffy::style::JustifyContent::Start.into(),
|
||||
JustifyContent::End => taffy::style::JustifyContent::End.into(),
|
||||
JustifyContent::FlexStart => taffy::style::JustifyContent::FlexStart.into(),
|
||||
JustifyContent::FlexEnd => taffy::style::JustifyContent::FlexEnd.into(),
|
||||
JustifyContent::Center => taffy::style::JustifyContent::Center.into(),
|
||||
JustifyContent::SpaceBetween => taffy::style::JustifyContent::SpaceBetween.into(),
|
||||
JustifyContent::SpaceAround => taffy::style::JustifyContent::SpaceAround.into(),
|
||||
JustifyContent::SpaceEvenly => taffy::style::JustifyContent::SpaceEvenly.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,6 +232,7 @@ impl From<Display> for taffy::style::Display {
|
|||
fn from(value: Display) -> Self {
|
||||
match value {
|
||||
Display::Flex => taffy::style::Display::Flex,
|
||||
Display::Grid => taffy::style::Display::Grid,
|
||||
Display::None => taffy::style::Display::None,
|
||||
}
|
||||
}
|
||||
|
@ -176,21 +249,6 @@ impl From<FlexDirection> for taffy::style::FlexDirection {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<JustifyContent> for taffy::style::JustifyContent {
|
||||
fn from(value: JustifyContent) -> Self {
|
||||
match value {
|
||||
JustifyContent::Start => taffy::style::JustifyContent::Start,
|
||||
JustifyContent::End => taffy::style::JustifyContent::End,
|
||||
JustifyContent::FlexStart => taffy::style::JustifyContent::FlexStart,
|
||||
JustifyContent::FlexEnd => taffy::style::JustifyContent::FlexEnd,
|
||||
JustifyContent::Center => taffy::style::JustifyContent::Center,
|
||||
JustifyContent::SpaceBetween => taffy::style::JustifyContent::SpaceBetween,
|
||||
JustifyContent::SpaceAround => taffy::style::JustifyContent::SpaceAround,
|
||||
JustifyContent::SpaceEvenly => taffy::style::JustifyContent::SpaceEvenly,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PositionType> for taffy::style::Position {
|
||||
fn from(value: PositionType) -> Self {
|
||||
match value {
|
||||
|
@ -210,12 +268,140 @@ impl From<FlexWrap> for taffy::style::FlexWrap {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<GridAutoFlow> for taffy::style::GridAutoFlow {
|
||||
fn from(value: GridAutoFlow) -> Self {
|
||||
match value {
|
||||
GridAutoFlow::Row => taffy::style::GridAutoFlow::Row,
|
||||
GridAutoFlow::RowDense => taffy::style::GridAutoFlow::RowDense,
|
||||
GridAutoFlow::Column => taffy::style::GridAutoFlow::Column,
|
||||
GridAutoFlow::ColumnDense => taffy::style::GridAutoFlow::ColumnDense,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GridPlacement> for taffy::geometry::Line<taffy::style::GridPlacement> {
|
||||
fn from(value: GridPlacement) -> Self {
|
||||
let span = value.span.unwrap_or(1).max(1);
|
||||
match (value.start, value.end) {
|
||||
(Some(start), Some(end)) => taffy::geometry::Line {
|
||||
start: style_helpers::line(start),
|
||||
end: style_helpers::line(end),
|
||||
},
|
||||
(Some(start), None) => taffy::geometry::Line {
|
||||
start: style_helpers::line(start),
|
||||
end: style_helpers::span(span),
|
||||
},
|
||||
(None, Some(end)) => taffy::geometry::Line {
|
||||
start: style_helpers::span(span),
|
||||
end: style_helpers::line(end),
|
||||
},
|
||||
(None, None) => style_helpers::span(span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MinTrackSizingFunction {
|
||||
fn into_taffy(self, context: &LayoutContext) -> taffy::style::MinTrackSizingFunction {
|
||||
match self {
|
||||
MinTrackSizingFunction::Px(val) => taffy::style::MinTrackSizingFunction::Fixed(
|
||||
Val::Px(val).into_length_percentage(context),
|
||||
),
|
||||
MinTrackSizingFunction::Percent(val) => taffy::style::MinTrackSizingFunction::Fixed(
|
||||
Val::Percent(val).into_length_percentage(context),
|
||||
),
|
||||
MinTrackSizingFunction::Auto => taffy::style::MinTrackSizingFunction::Auto,
|
||||
MinTrackSizingFunction::MinContent => taffy::style::MinTrackSizingFunction::MinContent,
|
||||
MinTrackSizingFunction::MaxContent => taffy::style::MinTrackSizingFunction::MaxContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MaxTrackSizingFunction {
|
||||
fn into_taffy(self, context: &LayoutContext) -> taffy::style::MaxTrackSizingFunction {
|
||||
match self {
|
||||
MaxTrackSizingFunction::Px(val) => taffy::style::MaxTrackSizingFunction::Fixed(
|
||||
Val::Px(val).into_length_percentage(context),
|
||||
),
|
||||
MaxTrackSizingFunction::Percent(val) => taffy::style::MaxTrackSizingFunction::Fixed(
|
||||
Val::Percent(val).into_length_percentage(context),
|
||||
),
|
||||
MaxTrackSizingFunction::Auto => taffy::style::MaxTrackSizingFunction::Auto,
|
||||
MaxTrackSizingFunction::MinContent => taffy::style::MaxTrackSizingFunction::MinContent,
|
||||
MaxTrackSizingFunction::MaxContent => taffy::style::MaxTrackSizingFunction::MaxContent,
|
||||
MaxTrackSizingFunction::FitContentPx(val) => {
|
||||
taffy::style::MaxTrackSizingFunction::FitContent(
|
||||
Val::Px(val).into_length_percentage(context),
|
||||
)
|
||||
}
|
||||
MaxTrackSizingFunction::FitContentPercent(val) => {
|
||||
taffy::style::MaxTrackSizingFunction::FitContent(
|
||||
Val::Percent(val).into_length_percentage(context),
|
||||
)
|
||||
}
|
||||
MaxTrackSizingFunction::Fraction(fraction) => {
|
||||
taffy::style::MaxTrackSizingFunction::Fraction(fraction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GridTrack {
|
||||
fn into_taffy_track(
|
||||
self,
|
||||
context: &LayoutContext,
|
||||
) -> taffy::style::NonRepeatedTrackSizingFunction {
|
||||
let min = self.min_sizing_function.into_taffy(context);
|
||||
let max = self.max_sizing_function.into_taffy(context);
|
||||
taffy::style_helpers::minmax(min, max)
|
||||
}
|
||||
}
|
||||
|
||||
impl RepeatedGridTrack {
|
||||
fn clone_into_repeated_taffy_track(
|
||||
&self,
|
||||
context: &LayoutContext,
|
||||
) -> taffy::style::TrackSizingFunction {
|
||||
if self.tracks.len() == 1 && self.repetition == GridTrackRepetition::Count(1) {
|
||||
let min = self.tracks[0].min_sizing_function.into_taffy(context);
|
||||
let max = self.tracks[0].max_sizing_function.into_taffy(context);
|
||||
let taffy_track = taffy::style_helpers::minmax(min, max);
|
||||
taffy::style::TrackSizingFunction::Single(taffy_track)
|
||||
} else {
|
||||
let taffy_tracks: Vec<_> = self
|
||||
.tracks
|
||||
.iter()
|
||||
.map(|track| {
|
||||
let min = track.min_sizing_function.into_taffy(context);
|
||||
let max = track.max_sizing_function.into_taffy(context);
|
||||
taffy::style_helpers::minmax(min, max)
|
||||
})
|
||||
.collect();
|
||||
|
||||
match self.repetition {
|
||||
GridTrackRepetition::Count(count) => {
|
||||
taffy::style_helpers::repeat(count, taffy_tracks)
|
||||
}
|
||||
GridTrackRepetition::AutoFit => taffy::style_helpers::repeat(
|
||||
taffy::style::GridTrackRepetition::AutoFit,
|
||||
taffy_tracks,
|
||||
),
|
||||
GridTrackRepetition::AutoFill => taffy::style_helpers::repeat(
|
||||
taffy::style::GridTrackRepetition::AutoFill,
|
||||
taffy_tracks,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_convert_from() {
|
||||
use taffy::style_helpers as sh;
|
||||
|
||||
let bevy_style = crate::Style {
|
||||
display: Display::Flex,
|
||||
position_type: PositionType::Absolute,
|
||||
|
@ -229,6 +415,8 @@ mod tests {
|
|||
align_items: AlignItems::Baseline,
|
||||
align_self: AlignSelf::Start,
|
||||
align_content: AlignContent::SpaceAround,
|
||||
justify_items: JustifyItems::Default,
|
||||
justify_self: JustifySelf::Center,
|
||||
justify_content: JustifyContent::SpaceEvenly,
|
||||
margin: UiRect {
|
||||
left: Val::Percent(0.),
|
||||
|
@ -269,6 +457,25 @@ mod tests {
|
|||
width: Val::Px(0.),
|
||||
height: Val::Percent(0.),
|
||||
},
|
||||
grid_auto_flow: GridAutoFlow::ColumnDense,
|
||||
grid_template_rows: vec![
|
||||
GridTrack::px(10.0),
|
||||
GridTrack::percent(50.0),
|
||||
GridTrack::fr(1.0),
|
||||
],
|
||||
grid_template_columns: RepeatedGridTrack::px(5, 10.0),
|
||||
grid_auto_rows: vec![
|
||||
GridTrack::fit_content_px(10.0),
|
||||
GridTrack::fit_content_percent(25.0),
|
||||
GridTrack::flex(2.0),
|
||||
],
|
||||
grid_auto_columns: vec![
|
||||
GridTrack::auto(),
|
||||
GridTrack::min_content(),
|
||||
GridTrack::max_content(),
|
||||
],
|
||||
grid_column: GridPlacement::start(4),
|
||||
grid_row: GridPlacement::span(3),
|
||||
};
|
||||
let viewport_values = LayoutContext::new(1.0, bevy_math::Vec2::new(800., 600.));
|
||||
let taffy_style = from_style(&viewport_values, &bevy_style);
|
||||
|
@ -308,6 +515,11 @@ mod tests {
|
|||
taffy_style.justify_content,
|
||||
Some(taffy::style::JustifyContent::SpaceEvenly)
|
||||
);
|
||||
assert_eq!(taffy_style.justify_items, None);
|
||||
assert_eq!(
|
||||
taffy_style.justify_self,
|
||||
Some(taffy::style::JustifySelf::Center)
|
||||
);
|
||||
assert!(matches!(
|
||||
taffy_style.margin.left,
|
||||
taffy::style::LengthPercentageAuto::Percent(_)
|
||||
|
@ -395,6 +607,38 @@ mod tests {
|
|||
taffy_style.gap.height,
|
||||
taffy::style::LengthPercentage::Percent(0.)
|
||||
);
|
||||
assert_eq!(
|
||||
taffy_style.grid_auto_flow,
|
||||
taffy::style::GridAutoFlow::ColumnDense
|
||||
);
|
||||
assert_eq!(
|
||||
taffy_style.grid_template_rows,
|
||||
vec![sh::points(10.0), sh::percent(0.5), sh::fr(1.0)]
|
||||
);
|
||||
assert_eq!(
|
||||
taffy_style.grid_template_columns,
|
||||
vec![sh::repeat(5, vec![sh::points(10.0)])]
|
||||
);
|
||||
assert_eq!(
|
||||
taffy_style.grid_auto_rows,
|
||||
vec![
|
||||
sh::fit_content(taffy::style::LengthPercentage::Points(10.0)),
|
||||
sh::fit_content(taffy::style::LengthPercentage::Percent(0.25)),
|
||||
sh::minmax(sh::points(0.0), sh::fr(2.0)),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
taffy_style.grid_auto_columns,
|
||||
vec![sh::auto(), sh::min_content(), sh::max_content()]
|
||||
);
|
||||
assert_eq!(
|
||||
taffy_style.grid_column,
|
||||
taffy::geometry::Line {
|
||||
start: sh::line(4),
|
||||
end: sh::span(1)
|
||||
}
|
||||
);
|
||||
assert_eq!(taffy_style.grid_row, sh::span(3));
|
||||
}
|
||||
|
||||
#[test]
|
|
@ -42,32 +42,29 @@ impl LayoutContext {
|
|||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct FlexSurface {
|
||||
pub struct UiSurface {
|
||||
entity_to_taffy: HashMap<Entity, taffy::node::Node>,
|
||||
window_nodes: HashMap<Entity, taffy::node::Node>,
|
||||
taffy: Taffy,
|
||||
}
|
||||
|
||||
// SAFETY: as long as MeasureFunc is Send + Sync. https://github.com/DioxusLabs/taffy/issues/146
|
||||
unsafe impl Send for FlexSurface {}
|
||||
unsafe impl Sync for FlexSurface {}
|
||||
|
||||
fn _assert_send_sync_flex_surface_impl_safe() {
|
||||
fn _assert_send_sync_ui_surface_impl_safe() {
|
||||
fn _assert_send_sync<T: Send + Sync>() {}
|
||||
_assert_send_sync::<HashMap<Entity, taffy::node::Node>>();
|
||||
_assert_send_sync::<Taffy>();
|
||||
_assert_send_sync::<UiSurface>();
|
||||
}
|
||||
|
||||
impl fmt::Debug for FlexSurface {
|
||||
impl fmt::Debug for UiSurface {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("FlexSurface")
|
||||
f.debug_struct("UiSurface")
|
||||
.field("entity_to_taffy", &self.entity_to_taffy)
|
||||
.field("window_nodes", &self.window_nodes)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FlexSurface {
|
||||
impl Default for UiSurface {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entity_to_taffy: Default::default(),
|
||||
|
@ -77,7 +74,7 @@ impl Default for FlexSurface {
|
|||
}
|
||||
}
|
||||
|
||||
impl FlexSurface {
|
||||
impl UiSurface {
|
||||
pub fn upsert_node(&mut self, entity: Entity, style: &Style, context: &LayoutContext) {
|
||||
let mut added = false;
|
||||
let taffy = &mut self.taffy;
|
||||
|
@ -217,35 +214,35 @@ without UI components as a child of an entity with UI components, results may be
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_layout(&self, entity: Entity) -> Result<&taffy::layout::Layout, FlexError> {
|
||||
pub fn get_layout(&self, entity: Entity) -> Result<&taffy::layout::Layout, LayoutError> {
|
||||
if let Some(taffy_node) = self.entity_to_taffy.get(&entity) {
|
||||
self.taffy
|
||||
.layout(*taffy_node)
|
||||
.map_err(FlexError::TaffyError)
|
||||
.map_err(LayoutError::TaffyError)
|
||||
} else {
|
||||
warn!(
|
||||
"Styled child in a non-UI entity hierarchy. You are using an entity \
|
||||
with UI components as a child of an entity without UI components, results may be unexpected."
|
||||
);
|
||||
Err(FlexError::InvalidHierarchy)
|
||||
Err(LayoutError::InvalidHierarchy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FlexError {
|
||||
pub enum LayoutError {
|
||||
InvalidHierarchy,
|
||||
TaffyError(taffy::error::TaffyError),
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn flex_node_system(
|
||||
pub fn ui_layout_system(
|
||||
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
|
||||
windows: Query<(Entity, &Window)>,
|
||||
ui_scale: Res<UiScale>,
|
||||
mut scale_factor_events: EventReader<WindowScaleFactorChanged>,
|
||||
mut resize_events: EventReader<bevy_window::WindowResized>,
|
||||
mut flex_surface: ResMut<FlexSurface>,
|
||||
mut ui_surface: ResMut<UiSurface>,
|
||||
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
||||
node_query: Query<(Entity, &Style, Option<&CalculatedSize>), (With<Node>, Changed<Style>)>,
|
||||
full_node_query: Query<(Entity, &Style, Option<&CalculatedSize>), With<Node>>,
|
||||
|
@ -281,7 +278,7 @@ pub fn flex_node_system(
|
|||
|
||||
// update window root nodes
|
||||
for (entity, window) in windows.iter() {
|
||||
flex_surface.update_window(entity, &window.resolution);
|
||||
ui_surface.update_window(entity, &window.resolution);
|
||||
}
|
||||
|
||||
let scale_factor = logical_to_physical_factor * ui_scale.scale;
|
||||
|
@ -289,7 +286,7 @@ pub fn flex_node_system(
|
|||
let viewport_values = LayoutContext::new(scale_factor, physical_size);
|
||||
|
||||
fn update_changed<F: ReadOnlyWorldQuery>(
|
||||
flex_surface: &mut FlexSurface,
|
||||
ui_surface: &mut UiSurface,
|
||||
viewport_values: &LayoutContext,
|
||||
query: Query<(Entity, &Style, Option<&CalculatedSize>), F>,
|
||||
) {
|
||||
|
@ -297,45 +294,45 @@ pub fn flex_node_system(
|
|||
for (entity, style, calculated_size) in &query {
|
||||
// TODO: remove node from old hierarchy if its root has changed
|
||||
if let Some(calculated_size) = calculated_size {
|
||||
flex_surface.upsert_leaf(entity, style, calculated_size, viewport_values);
|
||||
ui_surface.upsert_leaf(entity, style, calculated_size, viewport_values);
|
||||
} else {
|
||||
flex_surface.upsert_node(entity, style, viewport_values);
|
||||
ui_surface.upsert_node(entity, style, viewport_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !scale_factor_events.is_empty() || ui_scale.is_changed() || resized {
|
||||
scale_factor_events.clear();
|
||||
update_changed(&mut flex_surface, &viewport_values, full_node_query);
|
||||
update_changed(&mut ui_surface, &viewport_values, full_node_query);
|
||||
} else {
|
||||
update_changed(&mut flex_surface, &viewport_values, node_query);
|
||||
update_changed(&mut ui_surface, &viewport_values, node_query);
|
||||
}
|
||||
|
||||
for (entity, style, calculated_size) in &changed_size_query {
|
||||
flex_surface.upsert_leaf(entity, style, calculated_size, &viewport_values);
|
||||
ui_surface.upsert_leaf(entity, style, calculated_size, &viewport_values);
|
||||
}
|
||||
|
||||
// clean up removed nodes
|
||||
flex_surface.remove_entities(removed_nodes.iter());
|
||||
ui_surface.remove_entities(removed_nodes.iter());
|
||||
|
||||
// When a `CalculatedSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node.
|
||||
for entity in removed_calculated_sizes.iter() {
|
||||
flex_surface.try_remove_measure(entity);
|
||||
ui_surface.try_remove_measure(entity);
|
||||
}
|
||||
|
||||
// update window children (for now assuming all Nodes live in the primary window)
|
||||
flex_surface.set_window_children(primary_window_entity, root_node_query.iter());
|
||||
ui_surface.set_window_children(primary_window_entity, root_node_query.iter());
|
||||
|
||||
// update and remove children
|
||||
for entity in removed_children.iter() {
|
||||
flex_surface.try_remove_children(entity);
|
||||
ui_surface.try_remove_children(entity);
|
||||
}
|
||||
for (entity, children) in &children_query {
|
||||
flex_surface.update_children(entity, children);
|
||||
ui_surface.update_children(entity, children);
|
||||
}
|
||||
|
||||
// compute layouts
|
||||
flex_surface.compute_window_layouts();
|
||||
ui_surface.compute_window_layouts();
|
||||
|
||||
let physical_to_logical_factor = 1. / logical_to_physical_factor;
|
||||
|
||||
|
@ -343,7 +340,7 @@ pub fn flex_node_system(
|
|||
|
||||
// PERF: try doing this incrementally
|
||||
for (entity, mut node, mut transform, parent) in &mut node_transform_query {
|
||||
let layout = flex_surface.get_layout(entity).unwrap();
|
||||
let layout = ui_surface.get_layout(entity).unwrap();
|
||||
let new_size = Vec2::new(
|
||||
to_logical(layout.size.width),
|
||||
to_logical(layout.size.height),
|
||||
|
@ -356,7 +353,7 @@ pub fn flex_node_system(
|
|||
new_position.x = to_logical(layout.location.x + layout.size.width / 2.0);
|
||||
new_position.y = to_logical(layout.location.y + layout.size.height / 2.0);
|
||||
if let Some(parent) = parent {
|
||||
if let Ok(parent_layout) = flex_surface.get_layout(**parent) {
|
||||
if let Ok(parent_layout) = ui_surface.get_layout(**parent) {
|
||||
new_position.x -= to_logical(parent_layout.size.width / 2.0);
|
||||
new_position.y -= to_logical(parent_layout.size.height / 2.0);
|
||||
}
|
|
@ -3,10 +3,10 @@
|
|||
//! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games
|
||||
//! # Basic usage
|
||||
//! Spawn UI elements with [`node_bundles::ButtonBundle`], [`node_bundles::ImageBundle`], [`node_bundles::TextBundle`] and [`node_bundles::NodeBundle`]
|
||||
//! This UI is laid out with the Flexbox layout model (see <https://cssreference.io/flexbox/>)
|
||||
mod flex;
|
||||
//! This UI is laid out with the Flexbox and CSS Grid layout models (see <https://cssreference.io/flexbox/>)
|
||||
mod focus;
|
||||
mod geometry;
|
||||
mod layout;
|
||||
mod render;
|
||||
mod stack;
|
||||
mod ui_node;
|
||||
|
@ -22,9 +22,9 @@ pub mod widget;
|
|||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_render::camera::CameraUpdateSystem;
|
||||
use bevy_render::extract_component::ExtractComponentPlugin;
|
||||
pub use flex::*;
|
||||
pub use focus::*;
|
||||
pub use geometry::*;
|
||||
pub use layout::*;
|
||||
pub use measurement::*;
|
||||
pub use render::*;
|
||||
pub use ui_node::*;
|
||||
|
@ -54,8 +54,8 @@ pub struct UiPlugin;
|
|||
/// The label enum labeling the types of systems in the Bevy UI
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
pub enum UiSystem {
|
||||
/// After this label, the ui flex state has been updated
|
||||
Flex,
|
||||
/// After this label, the ui layout state has been updated
|
||||
Layout,
|
||||
/// After this label, input interactions with UI entities have been updated for this frame
|
||||
Focus,
|
||||
/// After this label, the [`UiStack`] resource has been updated
|
||||
|
@ -81,7 +81,7 @@ impl Default for UiScale {
|
|||
impl Plugin for UiPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugin(ExtractComponentPlugin::<UiCameraConfig>::default())
|
||||
.init_resource::<FlexSurface>()
|
||||
.init_resource::<UiSurface>()
|
||||
.init_resource::<UiScale>()
|
||||
.init_resource::<UiStack>()
|
||||
.register_type::<AlignContent>()
|
||||
|
@ -92,9 +92,15 @@ impl Plugin for UiPlugin {
|
|||
.register_type::<Display>()
|
||||
.register_type::<FlexDirection>()
|
||||
.register_type::<FlexWrap>()
|
||||
.register_type::<GridAutoFlow>()
|
||||
.register_type::<GridPlacement>()
|
||||
.register_type::<GridTrack>()
|
||||
.register_type::<RepeatedGridTrack>()
|
||||
.register_type::<FocusPolicy>()
|
||||
.register_type::<Interaction>()
|
||||
.register_type::<JustifyContent>()
|
||||
.register_type::<JustifyItems>()
|
||||
.register_type::<JustifySelf>()
|
||||
.register_type::<Node>()
|
||||
// NOTE: used by Style::aspect_ratio
|
||||
.register_type::<Option<f32>>()
|
||||
|
@ -116,8 +122,8 @@ impl Plugin for UiPlugin {
|
|||
#[cfg(feature = "bevy_text")]
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
widget::measure_text_system
|
||||
.before(UiSystem::Flex)
|
||||
widget::text_system
|
||||
.before(UiSystem::Layout)
|
||||
// Potential conflict: `Assets<Image>`
|
||||
// In practice, they run independently since `bevy_render::camera_update_system`
|
||||
// will only ever observe its own render target, and `widget::text_system`
|
||||
|
@ -131,7 +137,7 @@ impl Plugin for UiPlugin {
|
|||
#[cfg(feature = "bevy_text")]
|
||||
app.add_plugin(accessibility::AccessibilityPlugin);
|
||||
app.add_systems(PostUpdate, {
|
||||
let system = widget::update_image_calculated_size_system.before(UiSystem::Flex);
|
||||
let system = widget::update_image_calculated_size_system.before(UiSystem::Layout);
|
||||
// Potential conflicts: `Assets<Image>`
|
||||
// They run independently since `widget::image_node_system` will only ever observe
|
||||
// its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout`
|
||||
|
@ -146,12 +152,11 @@ impl Plugin for UiPlugin {
|
|||
.add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
flex_node_system
|
||||
.in_set(UiSystem::Flex)
|
||||
ui_layout_system
|
||||
.in_set(UiSystem::Layout)
|
||||
.before(TransformSystem::TransformPropagate),
|
||||
ui_stack_system.in_set(UiSystem::Stack),
|
||||
update_clipping_system.after(TransformSystem::TransformPropagate),
|
||||
widget::text_system.after(UiSystem::Flex),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
@ -20,7 +20,8 @@ use bevy_transform::prelude::{GlobalTransform, Transform};
|
|||
pub struct NodeBundle {
|
||||
/// Describes the logical size of the node
|
||||
pub node: Node,
|
||||
/// Describes the style including flexbox settings
|
||||
/// Styles which control the layout (size and position) of the node and it's children
|
||||
/// In some cases these styles also affect how the node drawn/painted.
|
||||
pub style: Style,
|
||||
/// The background color, which serves as a "fill" for this node
|
||||
pub background_color: BackgroundColor,
|
||||
|
@ -65,8 +66,12 @@ impl Default for NodeBundle {
|
|||
#[derive(Bundle, Clone, Debug, Default)]
|
||||
pub struct ImageBundle {
|
||||
/// Describes the logical size of the node
|
||||
///
|
||||
/// This field is automatically managed by the UI layout system.
|
||||
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
|
||||
pub node: Node,
|
||||
/// Describes the style including flexbox settings
|
||||
/// Styles which control the layout (size and position) of the node and it's children
|
||||
/// In some cases these styles also affect how the node drawn/painted.
|
||||
pub style: Style,
|
||||
/// The calculated size based on the given image
|
||||
pub calculated_size: CalculatedSize,
|
||||
|
@ -106,7 +111,8 @@ pub struct ImageBundle {
|
|||
pub struct TextBundle {
|
||||
/// Describes the logical size of the node
|
||||
pub node: Node,
|
||||
/// Describes the style including flexbox settings
|
||||
/// Styles which control the layout (size and position) of the node and it's children
|
||||
/// In some cases these styles also affect how the node drawn/painted.
|
||||
pub style: Style,
|
||||
/// Contains the text of the node
|
||||
pub text: Text,
|
||||
|
@ -186,7 +192,7 @@ impl TextBundle {
|
|||
}
|
||||
|
||||
/// Returns this [`TextBundle`] with a new [`Style`].
|
||||
pub const fn with_style(mut self, style: Style) -> Self {
|
||||
pub fn with_style(mut self, style: Style) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
@ -205,7 +211,8 @@ pub struct ButtonBundle {
|
|||
pub node: Node,
|
||||
/// Marker component that signals this node is a button
|
||||
pub button: Button,
|
||||
/// Describes the style including flexbox settings
|
||||
/// Styles which control the layout (size and position) of the node and it's children
|
||||
/// In some cases these styles also affect how the node drawn/painted.
|
||||
pub style: Style,
|
||||
/// Describes whether and how the button has been interacted with by the input
|
||||
pub interaction: Interaction,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -331,6 +331,7 @@ Example | Description
|
|||
Example | Description
|
||||
--- | ---
|
||||
[Button](../examples/ui/button.rs) | Illustrates creating and updating a button
|
||||
[CSS Grid](../examples/ui/grid.rs) | An example for CSS Grid layout
|
||||
[Flex Layout](../examples/ui/flex_layout.rs) | Demonstrates how the AlignItems and JustifyContent properties can be composed to layout nodes and position text
|
||||
[Font Atlas Debug](../examples/ui/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally)
|
||||
[Overflow and Clipping Debug](../examples/ui/overflow_debug.rs) | An example to debug overflow and clipping behavior
|
||||
|
|
218
examples/ui/grid.rs
Normal file
218
examples/ui/grid.rs
Normal file
|
@ -0,0 +1,218 @@
|
|||
//! Demonstrates how CSS Grid layout can be used to lay items out in a 2D grid
|
||||
use bevy::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
resolution: [800., 600.].into(),
|
||||
title: "Bevy CSS Grid Layout Example".to_string(),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}))
|
||||
.add_systems(Startup, spawn_layout)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn spawn_layout(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
let font = asset_server.load("fonts/FiraSans-Bold.ttf");
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
|
||||
// Top-level grid (app frame)
|
||||
commands
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
/// Use the CSS Grid algorithm for laying out this node
|
||||
display: Display::Grid,
|
||||
/// Make node fill the entirety it's parent (in this case the window)
|
||||
size: Size::all(Val::Percent(100.)),
|
||||
/// Set the grid to have 2 columns with sizes [min-content, minmax(0, 1fr)]
|
||||
/// - The first column will size to the size of it's contents
|
||||
/// - The second column will take up the remaining available space
|
||||
grid_template_columns: vec![GridTrack::min_content(), GridTrack::flex(1.0)],
|
||||
/// Set the grid to have 3 rows with sizes [auto, minmax(0, 1fr), 20px]
|
||||
/// - The first row will size to the size of it's contents
|
||||
/// - The second row take up remaining available space (after rows 1 and 3 have both been sized)
|
||||
/// - The third row will be exactly 20px high
|
||||
grid_template_rows: vec![
|
||||
GridTrack::auto(),
|
||||
GridTrack::flex(1.0),
|
||||
GridTrack::px(20.),
|
||||
],
|
||||
..default()
|
||||
},
|
||||
background_color: BackgroundColor(Color::WHITE),
|
||||
..default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
// Header
|
||||
builder
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
display: Display::Grid,
|
||||
/// Make this node span two grid columns so that it takes up the entire top tow
|
||||
grid_column: GridPlacement::span(2),
|
||||
padding: UiRect::all(Val::Px(6.0)),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
spawn_nested_text_bundle(builder, font.clone(), "Bevy CSS Grid Layout Example");
|
||||
});
|
||||
|
||||
// Main content grid (auto placed in row 2, column 1)
|
||||
builder
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
/// Make the height of the node fill its parent
|
||||
size: Size::height(Val::Percent(100.0)),
|
||||
/// Make the grid have a 1:1 aspect ratio meaning it will scale as an exact square
|
||||
/// As the height is set explicitly, this means the width will adjust to match the height
|
||||
aspect_ratio: Some(1.0),
|
||||
/// Use grid layout for this node
|
||||
display: Display::Grid,
|
||||
// Add 24px of padding around the grid
|
||||
padding: UiRect::all(Val::Px(24.0)),
|
||||
/// Set the grid to have 4 columns all with sizes minmax(0, 1fr)
|
||||
/// This creates 4 exactly evenly sized columns
|
||||
grid_template_columns: RepeatedGridTrack::flex(4, 1.0),
|
||||
/// Set the grid to have 4 rows all with sizes minmax(0, 1fr)
|
||||
/// This creates 4 exactly evenly sized rows
|
||||
grid_template_rows: RepeatedGridTrack::flex(4, 1.0),
|
||||
/// Set a 12px gap/gutter between rows and columns
|
||||
gap: Size::all(Val::Px(12.0)),
|
||||
..default()
|
||||
},
|
||||
background_color: BackgroundColor(Color::DARK_GRAY),
|
||||
..default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
// Note there is no need to specify the position for each grid item. Grid items that are
|
||||
// not given an explicit position will be automatically positioned into the next available
|
||||
// grid cell. The order in which this is performed can be controlled using the grid_auto_flow
|
||||
// style property.
|
||||
|
||||
item_rect(builder, Color::ORANGE);
|
||||
item_rect(builder, Color::BISQUE);
|
||||
item_rect(builder, Color::BLUE);
|
||||
item_rect(builder, Color::CRIMSON);
|
||||
|
||||
item_rect(builder, Color::CYAN);
|
||||
item_rect(builder, Color::ORANGE_RED);
|
||||
item_rect(builder, Color::DARK_GREEN);
|
||||
item_rect(builder, Color::FUCHSIA);
|
||||
|
||||
item_rect(builder, Color::TEAL);
|
||||
item_rect(builder, Color::ALICE_BLUE);
|
||||
item_rect(builder, Color::CRIMSON);
|
||||
item_rect(builder, Color::ANTIQUE_WHITE);
|
||||
|
||||
item_rect(builder, Color::YELLOW);
|
||||
item_rect(builder, Color::PINK);
|
||||
item_rect(builder, Color::YELLOW_GREEN);
|
||||
item_rect(builder, Color::SALMON);
|
||||
});
|
||||
|
||||
// Right side bar (auto placed in row 2, column 2)
|
||||
builder
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
display: Display::Grid,
|
||||
// Align content towards the start (top) in the vertical axis
|
||||
align_items: AlignItems::Start,
|
||||
// Align content towards the center in the horizontal axis
|
||||
justify_items: JustifyItems::Center,
|
||||
// Add 20px padding to the top
|
||||
padding: UiRect::top(Val::Px(20.)),
|
||||
..default()
|
||||
},
|
||||
background_color: BackgroundColor(Color::BLACK),
|
||||
..default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
builder.spawn(TextBundle::from_section(
|
||||
"Sidebar",
|
||||
TextStyle {
|
||||
font,
|
||||
font_size: 24.0,
|
||||
color: Color::WHITE,
|
||||
},
|
||||
));
|
||||
});
|
||||
|
||||
// Footer / status bar
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
// Make this node span two grid column so that it takes up the entire bottom row
|
||||
grid_column: GridPlacement::span(2),
|
||||
..default()
|
||||
},
|
||||
background_color: BackgroundColor(Color::WHITE),
|
||||
..default()
|
||||
});
|
||||
|
||||
// Modal (absolutely positioned on top of content - uncomment to view)
|
||||
// builder.spawn(NodeBundle {
|
||||
// style: Style {
|
||||
// position_type: PositionType::Absolute,
|
||||
// margin: UiRect {
|
||||
// top: Val::Px(100.),
|
||||
// bottom: Val::Auto,
|
||||
// left: Val::Auto,
|
||||
// right: Val::Auto,
|
||||
// },
|
||||
// size: Size {
|
||||
// width: Val::Percent(60.),
|
||||
// height: Val::Px(300.),
|
||||
// },
|
||||
// max_size: Size {
|
||||
// width: Val::Px(600.),
|
||||
// height: Val::Auto,
|
||||
// },
|
||||
// ..default()
|
||||
// },
|
||||
// background_color: BackgroundColor(Color::Rgba {
|
||||
// red: 255.0,
|
||||
// green: 255.0,
|
||||
// blue: 255.0,
|
||||
// alpha: 0.8,
|
||||
// }),
|
||||
// ..default()
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a coloured rectangle node. The node has size as it is assumed that it will be
|
||||
/// spawned as a child of a Grid container with `AlignItems::Stretch` and `JustifyItems::Stretch`
|
||||
/// which will allow it to take it's size from the size of the grid area it occupies.
|
||||
fn item_rect(builder: &mut ChildBuilder, color: Color) {
|
||||
builder
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
display: Display::Grid,
|
||||
padding: UiRect::all(Val::Px(3.0)),
|
||||
..default()
|
||||
},
|
||||
background_color: BackgroundColor(Color::BLACK),
|
||||
..default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
builder.spawn(NodeBundle {
|
||||
background_color: BackgroundColor(color),
|
||||
..default()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_nested_text_bundle(builder: &mut ChildBuilder, font: Handle<Font>, text: &str) {
|
||||
builder.spawn(TextBundle::from_section(
|
||||
text,
|
||||
TextStyle {
|
||||
font,
|
||||
font_size: 24.0,
|
||||
color: Color::BLACK,
|
||||
},
|
||||
));
|
||||
}
|
Loading…
Reference in a new issue