mirror of
https://github.com/bevyengine/bevy.git
synced 2026-07-01 00:05:45 -04:00
8ce450bd98
# Objective Text is wrapped to fit inside the node's content box but the node is sized as though the text isn't wrapped. Fixes #24664 ## Solution Subtract the total of the horizontal padding and border insets. ## Testing Added the replication from #24664 to `testbed_ui`'s `TextWrap` scene: ``` cargo run --example testbed_ui -- textwrap ``` <img width="500" alt="image" src="https://github.com/user-attachments/assets/e37884d1-7ae6-4aab-b16f-c5af004f5556" /> --- The other examples, particularly `text_debug`, shouldn't demonstrate any changes. --------- Co-authored-by: Dimitrios Loukadakis <dloukadakis@users.noreply.github.com>
2131 lines
85 KiB
Rust
2131 lines
85 KiB
Rust
//! UI testbed
|
|
//!
|
|
//! You can switch scene by pressing the spacebar
|
|
|
|
mod helpers;
|
|
|
|
use argh::FromArgs;
|
|
use bevy::prelude::*;
|
|
|
|
use helpers::Next;
|
|
|
|
#[derive(FromArgs)]
|
|
/// ui testbed
|
|
pub struct Args {
|
|
#[argh(positional)]
|
|
scene: Option<Scene>,
|
|
}
|
|
|
|
fn main() {
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
let args: Args = argh::from_env();
|
|
#[cfg(target_arch = "wasm32")]
|
|
let args: Args = Args::from_args(&[], &[]).unwrap();
|
|
|
|
let mut app = App::new();
|
|
app.add_plugins(DefaultPlugins.set(WindowPlugin {
|
|
primary_window: Some(Window {
|
|
// The ViewportCoords scene relies on these specific viewport dimensions,
|
|
// so let's explicitly define them and set resizable to false
|
|
resolution: (1280, 720).into(),
|
|
resizable: false,
|
|
..Default::default()
|
|
}),
|
|
..Default::default()
|
|
}))
|
|
.add_systems(OnEnter(Scene::Image), image::setup)
|
|
.add_systems(OnEnter(Scene::Text), text::setup)
|
|
.add_systems(OnEnter(Scene::Grid), grid::setup)
|
|
.add_systems(OnEnter(Scene::Borders), borders::setup)
|
|
.add_systems(OnEnter(Scene::BoxShadow), box_shadow::setup)
|
|
.add_systems(OnEnter(Scene::TextWrap), text_wrap::setup)
|
|
.add_systems(OnEnter(Scene::Overflow), overflow::setup)
|
|
.add_systems(OnEnter(Scene::Slice), slice::setup)
|
|
.add_systems(OnEnter(Scene::LayoutRounding), layout_rounding::setup)
|
|
.add_systems(OnEnter(Scene::LinearGradient), linear_gradient::setup)
|
|
.add_systems(OnEnter(Scene::RadialGradient), radial_gradient::setup)
|
|
.add_systems(OnEnter(Scene::Transformations), transformations::setup)
|
|
.add_systems(OnEnter(Scene::ViewportCoords), viewport_coords::setup)
|
|
.add_systems(OnEnter(Scene::OuterColor), outer_color::setup)
|
|
.add_systems(OnEnter(Scene::BoxedContent), boxed_content::setup)
|
|
.add_systems(OnEnter(Scene::EditableText), editable_text::setup)
|
|
.add_systems(Update, switch_scene);
|
|
|
|
match args.scene {
|
|
None => app.init_state::<Scene>(),
|
|
Some(scene) => app.insert_state(scene),
|
|
};
|
|
|
|
#[cfg(feature = "bevy_ui_debug")]
|
|
{
|
|
app.add_systems(OnEnter(Scene::DebugOutlines), debug_outlines::setup);
|
|
app.add_systems(OnExit(Scene::DebugOutlines), debug_outlines::teardown);
|
|
}
|
|
|
|
#[cfg(feature = "bevy_ci_testing")]
|
|
app.add_systems(Update, helpers::switch_scene_in_ci::<Scene>);
|
|
|
|
app.run();
|
|
}
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
|
|
#[states(scoped_entities)]
|
|
enum Scene {
|
|
#[default]
|
|
Image,
|
|
Text,
|
|
Grid,
|
|
Borders,
|
|
BoxShadow,
|
|
TextWrap,
|
|
Overflow,
|
|
Slice,
|
|
LayoutRounding,
|
|
LinearGradient,
|
|
RadialGradient,
|
|
Transformations,
|
|
#[cfg(feature = "bevy_ui_debug")]
|
|
DebugOutlines,
|
|
ViewportCoords,
|
|
OuterColor,
|
|
BoxedContent,
|
|
EditableText,
|
|
}
|
|
|
|
impl std::str::FromStr for Scene {
|
|
type Err = String;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let mut isit = Self::default();
|
|
while s.to_lowercase() != format!("{isit:?}").to_lowercase() {
|
|
isit = isit.next();
|
|
if isit == Self::default() {
|
|
return Err(format!("Invalid Scene name: {s}"));
|
|
}
|
|
}
|
|
Ok(isit)
|
|
}
|
|
}
|
|
|
|
impl Next for Scene {
|
|
fn next(&self) -> Self {
|
|
match self {
|
|
Scene::Image => Scene::Text,
|
|
Scene::Text => Scene::Grid,
|
|
Scene::Grid => Scene::Borders,
|
|
Scene::Borders => Scene::BoxShadow,
|
|
Scene::BoxShadow => Scene::TextWrap,
|
|
Scene::TextWrap => Scene::Overflow,
|
|
Scene::Overflow => Scene::Slice,
|
|
Scene::Slice => Scene::LayoutRounding,
|
|
Scene::LayoutRounding => Scene::LinearGradient,
|
|
Scene::LinearGradient => Scene::RadialGradient,
|
|
#[cfg(feature = "bevy_ui_debug")]
|
|
Scene::RadialGradient => Scene::DebugOutlines,
|
|
#[cfg(feature = "bevy_ui_debug")]
|
|
Scene::DebugOutlines => Scene::Transformations,
|
|
#[cfg(not(feature = "bevy_ui_debug"))]
|
|
Scene::RadialGradient => Scene::Transformations,
|
|
Scene::Transformations => Scene::ViewportCoords,
|
|
Scene::ViewportCoords => Scene::OuterColor,
|
|
Scene::OuterColor => Scene::BoxedContent,
|
|
Scene::BoxedContent => Scene::EditableText,
|
|
Scene::EditableText => Scene::Image,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn switch_scene(
|
|
keyboard: Res<ButtonInput<KeyCode>>,
|
|
scene: Res<State<Scene>>,
|
|
mut next_scene: ResMut<NextState<Scene>>,
|
|
) {
|
|
if keyboard.just_pressed(KeyCode::Space) {
|
|
info!("Switching scene");
|
|
next_scene.set(scene.get().next());
|
|
}
|
|
}
|
|
|
|
mod image {
|
|
use bevy::color::palettes::css::DARK_GREY;
|
|
use bevy::prelude::*;
|
|
|
|
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::Image)));
|
|
commands
|
|
.spawn(Node {
|
|
width: percent(100.),
|
|
height: percent(100.),
|
|
flex_direction: FlexDirection::Column,
|
|
justify_content: JustifyContent::SpaceAround,
|
|
align_items: AlignItems::Stretch,
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
for [b, p] in [[0, 0], [10, 0], [0, 10], [10, 10]] {
|
|
for image_path in ["branding/icon.png", "branding/bevy_logo_dark.png"] {
|
|
parent
|
|
.spawn(Node {
|
|
justify_content: JustifyContent::SpaceAround,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
for visual_box in [
|
|
VisualBox::BorderBox,
|
|
VisualBox::PaddingBox,
|
|
VisualBox::ContentBox,
|
|
] {
|
|
parent.spawn((
|
|
ImageNode {
|
|
image: asset_server.load(image_path),
|
|
visual_box,
|
|
..default()
|
|
},
|
|
Node {
|
|
border: px(b).all(),
|
|
padding: px(p).all(),
|
|
width: px(100.),
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::Image),
|
|
Outline {
|
|
color: DARK_GREY.into(),
|
|
width: px(2.),
|
|
..default()
|
|
},
|
|
));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
mod text {
|
|
use bevy::{color::palettes::css::*, prelude::*, text::FontSmoothing};
|
|
|
|
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::Text)));
|
|
|
|
let mut container = commands.spawn((
|
|
Node {
|
|
flex_direction: FlexDirection::Column,
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::Text),
|
|
));
|
|
|
|
container.with_child((
|
|
Text::new("Hello World."),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
font_size: FontSize::Px(200.),
|
|
..default()
|
|
},
|
|
));
|
|
|
|
container.with_children(|builder| {
|
|
let mut grid = builder.spawn(Node {
|
|
display: Display::Grid,
|
|
grid_template_columns: vec![GridTrack::flex(1.0), GridTrack::flex(1.0)],
|
|
padding: UiRect::horizontal(px(5.)),
|
|
..default()
|
|
});
|
|
|
|
grid.with_children(|grid| {
|
|
for hinting in [FontHinting::Enabled, FontHinting::Disabled] {
|
|
let mut content = grid.spawn(Node {
|
|
flex_direction: FlexDirection::Column,
|
|
row_gap: px(5.),
|
|
..default()
|
|
});
|
|
|
|
content.with_child((
|
|
Text::new(format!("FontHinting::{:?}", hinting)),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
..default()
|
|
},
|
|
hinting,
|
|
));
|
|
|
|
content.with_child((
|
|
Text::new("white "),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
..default()
|
|
},
|
|
hinting,
|
|
children![
|
|
(TextSpan::new("red "), TextColor(RED.into()),),
|
|
(TextSpan::new("green "), TextColor(GREEN.into()),),
|
|
(TextSpan::new("blue "), TextColor(BLUE.into()),),
|
|
(
|
|
TextSpan::new("black"),
|
|
TextColor(Color::BLACK),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
..default()
|
|
},
|
|
TextBackgroundColor(Color::WHITE)
|
|
),
|
|
],
|
|
));
|
|
|
|
content.with_child((
|
|
Text::new(""),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
..default()
|
|
},
|
|
hinting,
|
|
children![
|
|
(
|
|
TextSpan::new("white "),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
..default()
|
|
}
|
|
),
|
|
(TextSpan::new("red "), TextColor(RED.into()),),
|
|
(TextSpan::new("green "), TextColor(GREEN.into()),),
|
|
(TextSpan::new("blue "), TextColor(BLUE.into()),),
|
|
(
|
|
TextSpan::new("black"),
|
|
TextColor(Color::BLACK),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
..default()
|
|
},
|
|
TextBackgroundColor(Color::WHITE)
|
|
),
|
|
],
|
|
));
|
|
|
|
content.with_child((
|
|
Text::new(""),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
..default()
|
|
},
|
|
hinting,
|
|
children![
|
|
(TextSpan::new(""), TextColor(YELLOW.into()),),
|
|
TextSpan::new(""),
|
|
(
|
|
TextSpan::new("white "),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
..default()
|
|
}
|
|
),
|
|
TextSpan::new(""),
|
|
(TextSpan::new("red "), TextColor(RED.into()),),
|
|
TextSpan::new(""),
|
|
TextSpan::new(""),
|
|
(TextSpan::new("green "), TextColor(GREEN.into()),),
|
|
(TextSpan::new(""), TextColor(YELLOW.into()),),
|
|
(TextSpan::new("blue "), TextColor(BLUE.into()),),
|
|
TextSpan::new(""),
|
|
(TextSpan::new(""), TextColor(YELLOW.into()),),
|
|
(
|
|
TextSpan::new("black"),
|
|
TextColor(Color::BLACK),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
..default()
|
|
},
|
|
TextBackgroundColor(Color::WHITE)
|
|
),
|
|
TextSpan::new(""),
|
|
],
|
|
));
|
|
|
|
content.with_child((
|
|
hinting,
|
|
Text::new("FiraSans_"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
children![
|
|
(
|
|
TextSpan::new("MonaSans_"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/MonaSans-VariableFont.ttf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
}
|
|
),
|
|
(
|
|
TextSpan::new("EBGaramond_"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/EBGaramond12-Regular.otf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
TextSpan::new("FiraMono"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
],
|
|
));
|
|
|
|
content.with_child((
|
|
hinting,
|
|
Text::new("FiraSans "),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
children![
|
|
(
|
|
TextSpan::new("MonaSans "),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/MonaSans-VariableFont.ttf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
}
|
|
),
|
|
(
|
|
TextSpan::new("EBGaramond "),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/EBGaramond12-Regular.otf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
TextSpan::new("FiraMono"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
],
|
|
));
|
|
|
|
content.with_child((
|
|
hinting,
|
|
Text::new("FiraSans "),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
children![
|
|
(
|
|
TextSpan::new("MonaSans_"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/MonaSans-VariableFont.ttf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
}
|
|
),
|
|
(
|
|
TextSpan::new("EBGaramond "),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/EBGaramond12-Regular.otf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
TextSpan::new("FiraMono"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
],
|
|
));
|
|
|
|
content.with_child((
|
|
hinting,
|
|
Text::new("FiraSans"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
children![
|
|
TextSpan::new(" "),
|
|
(
|
|
TextSpan::new("MonaSans"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/MonaSans-VariableFont.ttf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
}
|
|
),
|
|
TextSpan::new(" "),
|
|
(
|
|
TextSpan::new("EBGaramond"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/EBGaramond12-Regular.otf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
TextSpan::new(" "),
|
|
(
|
|
TextSpan::new("FiraMono"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
],
|
|
));
|
|
|
|
content.with_child((
|
|
hinting,
|
|
Text::new("Fira Sans_"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
children![
|
|
(
|
|
TextSpan::new("Mona Sans_"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/MonaSans-VariableFont.ttf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
}
|
|
),
|
|
(
|
|
TextSpan::new("EB Garamond_"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/EBGaramond12-Regular.otf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
TextSpan::new("Fira Mono"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
],
|
|
));
|
|
|
|
content.with_child((
|
|
hinting,
|
|
Text::new("FontWeight(100)_"),
|
|
TextFont {
|
|
font: "Mona Sans".into(),
|
|
font_size: FontSize::Px(25.),
|
|
weight: FontWeight(100),
|
|
..default()
|
|
},
|
|
children![
|
|
(
|
|
TextSpan::new("FontWeight(500)_"),
|
|
TextFont {
|
|
font: "Mona Sans".into(),
|
|
font_size: FontSize::Px(25.),
|
|
weight: FontWeight(500),
|
|
..default()
|
|
}
|
|
),
|
|
(
|
|
TextSpan::new("FontWeight(900)"),
|
|
TextFont {
|
|
font: "Mona Sans".into(),
|
|
font_size: FontSize::Px(25.),
|
|
weight: FontWeight(900),
|
|
..default()
|
|
},
|
|
),
|
|
],
|
|
));
|
|
|
|
content.with_child((
|
|
hinting,
|
|
Text::new("FiraSans_"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
weight: FontWeight(900),
|
|
..default()
|
|
},
|
|
children![
|
|
(
|
|
TextSpan::new("MonaSans_"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/MonaSans-VariableFont.ttf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
weight: FontWeight(700),
|
|
..default()
|
|
}
|
|
),
|
|
(
|
|
TextSpan::new("EBGaramond_"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/EBGaramond12-Regular.otf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
weight: FontWeight(500),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
TextSpan::new("FiraMono"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
weight: FontWeight(300),
|
|
..default()
|
|
},
|
|
),
|
|
],
|
|
));
|
|
|
|
content.with_child((
|
|
hinting,
|
|
Text::new("FiraSans\t"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
children![
|
|
(
|
|
TextSpan::new("MonaSans\t"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/MonaSans-VariableFont.ttf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
}
|
|
),
|
|
(
|
|
TextSpan::new("EBGaramond\t"),
|
|
TextFont {
|
|
font: asset_server
|
|
.load("fonts/EBGaramond12-Regular.otf")
|
|
.into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
TextSpan::new("FiraMono"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
..default()
|
|
},
|
|
),
|
|
],
|
|
));
|
|
|
|
for font_smoothing in [FontSmoothing::AntiAliased, FontSmoothing::None] {
|
|
content.with_child((
|
|
Text::new(format!("FontSmoothing::{:?}", font_smoothing)),
|
|
TextFont {
|
|
font: asset_server.load("fonts/MonaSans-VariableFont.ttf").into(),
|
|
font_size: FontSize::Px(25.),
|
|
font_smoothing,
|
|
..default()
|
|
},
|
|
));
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
mod grid {
|
|
use bevy::{color::palettes::css::*, prelude::*};
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::Grid)));
|
|
// Top-level grid (app frame)
|
|
commands.spawn((
|
|
Node {
|
|
display: Display::Grid,
|
|
width: percent(100),
|
|
height: percent(100),
|
|
grid_template_columns: vec![GridTrack::min_content(), GridTrack::flex(1.0)],
|
|
grid_template_rows: vec![
|
|
GridTrack::auto(),
|
|
GridTrack::flex(1.0),
|
|
GridTrack::px(40.),
|
|
],
|
|
..default()
|
|
},
|
|
BackgroundColor(Color::WHITE),
|
|
DespawnOnExit(super::Scene::Grid),
|
|
children![
|
|
// Header
|
|
(
|
|
Node {
|
|
display: Display::Grid,
|
|
grid_column: GridPlacement::span(2),
|
|
padding: UiRect::all(px(40)),
|
|
..default()
|
|
},
|
|
BackgroundColor(RED.into()),
|
|
),
|
|
// Main content grid (auto placed in row 2, column 1)
|
|
(
|
|
Node {
|
|
height: percent(100),
|
|
aspect_ratio: Some(1.0),
|
|
display: Display::Grid,
|
|
grid_template_columns: RepeatedGridTrack::flex(3, 1.0),
|
|
grid_template_rows: RepeatedGridTrack::flex(2, 1.0),
|
|
row_gap: px(12),
|
|
column_gap: px(12),
|
|
..default()
|
|
},
|
|
BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
|
|
children![
|
|
(Node::default(), BackgroundColor(ORANGE.into())),
|
|
(Node::default(), BackgroundColor(BISQUE.into())),
|
|
(Node::default(), BackgroundColor(BLUE.into())),
|
|
(Node::default(), BackgroundColor(CRIMSON.into())),
|
|
(Node::default(), BackgroundColor(AQUA.into())),
|
|
]
|
|
),
|
|
// Right side bar (auto placed in row 2, column 2)
|
|
(Node::DEFAULT, BackgroundColor(BLACK.into())),
|
|
],
|
|
));
|
|
}
|
|
}
|
|
|
|
mod borders {
|
|
use bevy::{color::palettes::css::*, prelude::*};
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::Borders)));
|
|
let root = commands
|
|
.spawn((
|
|
Node {
|
|
flex_wrap: FlexWrap::Wrap,
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::Borders),
|
|
))
|
|
.id();
|
|
|
|
// all the different combinations of border edges
|
|
let borders = [
|
|
UiRect::default(),
|
|
UiRect::all(px(20)),
|
|
UiRect::left(px(20)),
|
|
UiRect::vertical(px(20)),
|
|
UiRect {
|
|
left: px(40),
|
|
top: px(20),
|
|
..Default::default()
|
|
},
|
|
UiRect {
|
|
right: px(20),
|
|
bottom: px(30),
|
|
..Default::default()
|
|
},
|
|
UiRect {
|
|
right: px(20),
|
|
top: px(40),
|
|
bottom: px(20),
|
|
..Default::default()
|
|
},
|
|
UiRect {
|
|
left: px(20),
|
|
top: px(20),
|
|
bottom: px(20),
|
|
..Default::default()
|
|
},
|
|
UiRect {
|
|
left: px(20),
|
|
right: px(20),
|
|
bottom: px(40),
|
|
..Default::default()
|
|
},
|
|
];
|
|
|
|
let non_zero = |x, y| x != px(0) && y != px(0);
|
|
let border_size = |x, y| if non_zero(x, y) { f32::MAX } else { 0. };
|
|
|
|
for border in borders {
|
|
for rounded in [true, false] {
|
|
let border_node = commands
|
|
.spawn((
|
|
Node {
|
|
width: px(100),
|
|
height: px(100),
|
|
border,
|
|
margin: UiRect::all(px(30)),
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
border_radius: if rounded {
|
|
BorderRadius::px(
|
|
border_size(border.left, border.top),
|
|
border_size(border.right, border.top),
|
|
border_size(border.right, border.bottom),
|
|
border_size(border.left, border.bottom),
|
|
)
|
|
} else {
|
|
BorderRadius::ZERO
|
|
},
|
|
..default()
|
|
},
|
|
BackgroundColor(MAROON.into()),
|
|
BorderColor::all(RED),
|
|
Outline {
|
|
width: px(10),
|
|
offset: px(10),
|
|
color: Color::WHITE,
|
|
},
|
|
))
|
|
.id();
|
|
|
|
commands.entity(root).add_child(border_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mod box_shadow {
|
|
use bevy::{color::palettes::css::*, prelude::*};
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::BoxShadow)));
|
|
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
width: percent(100),
|
|
height: percent(100),
|
|
padding: UiRect::all(px(30)),
|
|
column_gap: px(200),
|
|
flex_wrap: FlexWrap::Wrap,
|
|
..default()
|
|
},
|
|
BackgroundColor(GREEN.into()),
|
|
DespawnOnExit(super::Scene::BoxShadow),
|
|
))
|
|
.with_children(|commands| {
|
|
let example_nodes = [
|
|
(
|
|
Vec2::splat(100.),
|
|
Vec2::ZERO,
|
|
10.,
|
|
0.,
|
|
BorderRadius::bottom_right(px(10)),
|
|
),
|
|
(Vec2::new(200., 50.), Vec2::ZERO, 10., 0., BorderRadius::MAX),
|
|
(
|
|
Vec2::new(100., 50.),
|
|
Vec2::ZERO,
|
|
10.,
|
|
10.,
|
|
BorderRadius::ZERO,
|
|
),
|
|
(
|
|
Vec2::splat(100.),
|
|
Vec2::splat(20.),
|
|
10.,
|
|
10.,
|
|
BorderRadius::bottom_right(px(10)),
|
|
),
|
|
(
|
|
Vec2::splat(100.),
|
|
Vec2::splat(50.),
|
|
0.,
|
|
10.,
|
|
BorderRadius::ZERO,
|
|
),
|
|
(
|
|
Vec2::new(50., 100.),
|
|
Vec2::splat(10.),
|
|
0.,
|
|
10.,
|
|
BorderRadius::MAX,
|
|
),
|
|
];
|
|
|
|
for (size, offset, spread, blur, border_radius) in example_nodes {
|
|
commands.spawn((
|
|
Node {
|
|
width: px(size.x),
|
|
height: px(size.y),
|
|
border: UiRect::all(px(2)),
|
|
border_radius,
|
|
..default()
|
|
},
|
|
BorderColor::all(WHITE),
|
|
BackgroundColor(BLUE.into()),
|
|
BoxShadow::new(
|
|
Color::BLACK.with_alpha(0.9),
|
|
percent(offset.x),
|
|
percent(offset.y),
|
|
percent(spread),
|
|
px(blur),
|
|
),
|
|
));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
mod text_wrap {
|
|
use bevy::prelude::*;
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::TextWrap)));
|
|
|
|
let root = commands
|
|
.spawn((
|
|
Node {
|
|
flex_direction: FlexDirection::Column,
|
|
width: px(200),
|
|
height: percent(100),
|
|
overflow: Overflow::clip_x(),
|
|
..default()
|
|
},
|
|
BackgroundColor(Color::BLACK),
|
|
DespawnOnExit(super::Scene::TextWrap),
|
|
))
|
|
.id();
|
|
|
|
for linebreak in [
|
|
LineBreak::AnyCharacter,
|
|
LineBreak::WordBoundary,
|
|
LineBreak::WordOrCharacter,
|
|
LineBreak::NoWrap,
|
|
] {
|
|
let messages = [
|
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.".to_string(),
|
|
"pneumonoultramicroscopicsilicovolcanoconiosis".to_string(),
|
|
];
|
|
|
|
for (j, message) in messages.into_iter().enumerate() {
|
|
commands.entity(root).with_child((
|
|
Text(message.clone()),
|
|
TextLayout::new(Justify::Left, linebreak),
|
|
BackgroundColor(Color::srgb(0.8 - j as f32 * 0.3, 0., 0.)),
|
|
));
|
|
}
|
|
}
|
|
|
|
commands.spawn((
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
padding: UiRect::px(10.0, 10.0, 8.0, 6.0),
|
|
top: px(300.0),
|
|
left: px(300.0),
|
|
width: vmin(30.0),
|
|
..default()
|
|
},
|
|
BackgroundColor::from(bevy::color::palettes::css::GREEN),
|
|
Text::new("initial text here"),
|
|
TextColor(Color::WHITE),
|
|
TextLayout {
|
|
justify: Justify::Left,
|
|
linebreak: LineBreak::WordBoundary,
|
|
},
|
|
DespawnOnExit(super::Scene::TextWrap),
|
|
));
|
|
}
|
|
}
|
|
|
|
mod overflow {
|
|
use bevy::{color::palettes::css::*, prelude::*};
|
|
|
|
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::Overflow)));
|
|
let image = asset_server.load("branding/icon.png");
|
|
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
width: percent(100),
|
|
height: percent(100),
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::SpaceAround,
|
|
..Default::default()
|
|
},
|
|
BackgroundColor(BLUE.into()),
|
|
DespawnOnExit(super::Scene::Overflow),
|
|
))
|
|
.with_children(|parent| {
|
|
for overflow in [
|
|
Overflow::visible(),
|
|
Overflow::clip_x(),
|
|
Overflow::clip_y(),
|
|
Overflow::clip(),
|
|
] {
|
|
parent
|
|
.spawn((
|
|
Node {
|
|
width: px(100),
|
|
height: px(100),
|
|
padding: UiRect {
|
|
left: px(25),
|
|
top: px(25),
|
|
..Default::default()
|
|
},
|
|
border: UiRect::all(px(5)),
|
|
overflow,
|
|
..default()
|
|
},
|
|
BorderColor::all(RED),
|
|
BackgroundColor(Color::WHITE),
|
|
))
|
|
.with_children(|parent| {
|
|
parent.spawn((
|
|
ImageNode::new(image.clone()),
|
|
Node {
|
|
min_width: px(100),
|
|
min_height: px(100),
|
|
..default()
|
|
},
|
|
Interaction::default(),
|
|
Outline {
|
|
width: px(2),
|
|
offset: px(2),
|
|
color: Color::NONE,
|
|
},
|
|
));
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
mod slice {
|
|
use bevy::prelude::*;
|
|
|
|
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::Slice)));
|
|
let image = asset_server.load("textures/fantasy_ui_borders/numbered_slices.png");
|
|
|
|
let slicer = TextureSlicer {
|
|
border: BorderRect::all(16.0),
|
|
center_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
|
|
sides_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
|
|
..default()
|
|
};
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
width: percent(100),
|
|
height: percent(100),
|
|
flex_direction: FlexDirection::Column,
|
|
justify_content: JustifyContent::SpaceAround,
|
|
align_content: AlignContent::Center,
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::Slice),
|
|
))
|
|
.with_children(|parent| {
|
|
for visual_box in [
|
|
VisualBox::BorderBox,
|
|
VisualBox::PaddingBox,
|
|
VisualBox::ContentBox,
|
|
] {
|
|
parent
|
|
.spawn(Node {
|
|
justify_content: JustifyContent::SpaceAround,
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
for [w, h] in [[200.0, 200.0], [300.0, 200.0], [150., 200.0]] {
|
|
parent.spawn((
|
|
Button,
|
|
ImageNode {
|
|
image: image.clone(),
|
|
image_mode: NodeImageMode::Sliced(slicer.clone()),
|
|
visual_box,
|
|
..default()
|
|
},
|
|
Node {
|
|
width: px(w),
|
|
height: px(h),
|
|
border: px(20.).all(),
|
|
padding: px(20.).all(),
|
|
..default()
|
|
},
|
|
Outline {
|
|
width: px(2.),
|
|
..default()
|
|
},
|
|
));
|
|
}
|
|
|
|
parent.spawn((
|
|
ImageNode {
|
|
image: asset_server
|
|
.load("textures/fantasy_ui_borders/panel-border-010.png"),
|
|
image_mode: NodeImageMode::Sliced(TextureSlicer {
|
|
border: BorderRect::all(22.0),
|
|
center_scale_mode: SliceScaleMode::Stretch,
|
|
sides_scale_mode: SliceScaleMode::Stretch,
|
|
max_corner_scale: 1.0,
|
|
}),
|
|
visual_box,
|
|
..Default::default()
|
|
},
|
|
Node {
|
|
width: px(200),
|
|
height: px(200),
|
|
border: px(20.).all(),
|
|
padding: px(20.).all(),
|
|
..default()
|
|
},
|
|
Outline {
|
|
color: bevy::color::palettes::css::DARK_CYAN.into(),
|
|
width: px(2.),
|
|
..default()
|
|
},
|
|
BackgroundColor(bevy::color::palettes::css::NAVY.into()),
|
|
));
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
mod layout_rounding {
|
|
use bevy::{color::palettes::css::*, prelude::*};
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::LayoutRounding)));
|
|
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
display: Display::Grid,
|
|
width: percent(100),
|
|
height: percent(100),
|
|
grid_template_rows: vec![RepeatedGridTrack::fr(10, 1.)],
|
|
..Default::default()
|
|
},
|
|
BackgroundColor(Color::WHITE),
|
|
DespawnOnExit(super::Scene::LayoutRounding),
|
|
))
|
|
.with_children(|commands| {
|
|
for i in 2..12 {
|
|
commands
|
|
.spawn(Node {
|
|
display: Display::Grid,
|
|
grid_template_columns: vec![RepeatedGridTrack::fr(i, 1.)],
|
|
..Default::default()
|
|
})
|
|
.with_children(|commands| {
|
|
for _ in 0..i {
|
|
commands.spawn((
|
|
Node {
|
|
border: UiRect::all(px(5)),
|
|
..Default::default()
|
|
},
|
|
BackgroundColor(MAROON.into()),
|
|
BorderColor::all(DARK_BLUE),
|
|
));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
mod linear_gradient {
|
|
use bevy::camera::Camera2d;
|
|
use bevy::color::palettes::css::BLUE;
|
|
use bevy::color::palettes::css::LIME;
|
|
use bevy::color::palettes::css::RED;
|
|
use bevy::color::palettes::css::YELLOW;
|
|
use bevy::color::Color;
|
|
use bevy::ecs::prelude::*;
|
|
use bevy::state::state_scoped::DespawnOnExit;
|
|
use bevy::text::TextFont;
|
|
use bevy::ui::AlignItems;
|
|
use bevy::ui::BackgroundGradient;
|
|
use bevy::ui::ColorStop;
|
|
use bevy::ui::GridPlacement;
|
|
use bevy::ui::InterpolationColorSpace;
|
|
use bevy::ui::JustifyContent;
|
|
use bevy::ui::LinearGradient;
|
|
use bevy::ui::Node;
|
|
use bevy::ui::PositionType;
|
|
use bevy::utils::default;
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::LinearGradient)));
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
flex_direction: bevy::ui::FlexDirection::Column,
|
|
width: bevy::ui::percent(100),
|
|
height: bevy::ui::percent(100),
|
|
justify_content: JustifyContent::Center,
|
|
align_items: AlignItems::Center,
|
|
row_gap: bevy::ui::px(5),
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::LinearGradient),
|
|
))
|
|
.with_children(|commands| {
|
|
let mut i = 0;
|
|
commands
|
|
.spawn(Node {
|
|
display: bevy::ui::Display::Grid,
|
|
row_gap: bevy::ui::px(4),
|
|
column_gap: bevy::ui::px(4),
|
|
..Default::default()
|
|
})
|
|
.with_children(|commands| {
|
|
for stops in [
|
|
vec![ColorStop::auto(RED), ColorStop::auto(YELLOW)],
|
|
vec![
|
|
ColorStop::auto(Color::BLACK),
|
|
ColorStop::auto(RED),
|
|
ColorStop::auto(Color::WHITE),
|
|
],
|
|
vec![
|
|
Color::hsl(180.71191, 0.0, 0.3137255).into(),
|
|
Color::hsl(180.71191, 0.5, 0.3137255).into(),
|
|
Color::hsl(180.71191, 1.0, 0.3137255).into(),
|
|
],
|
|
vec![
|
|
Color::hsl(180.71191, 0.825, 0.0).into(),
|
|
Color::hsl(180.71191, 0.825, 0.5).into(),
|
|
Color::hsl(180.71191, 0.825, 1.0).into(),
|
|
],
|
|
vec![
|
|
Color::hsl(0.0 + 0.0001, 1.0, 0.5).into(),
|
|
Color::hsl(180.0, 1.0, 0.5).into(),
|
|
Color::hsl(360.0 - 0.0001, 1.0, 0.5).into(),
|
|
],
|
|
vec![
|
|
Color::WHITE.into(),
|
|
RED.into(),
|
|
LIME.into(),
|
|
BLUE.into(),
|
|
Color::BLACK.into(),
|
|
],
|
|
] {
|
|
for color_space in [
|
|
InterpolationColorSpace::LinearRgba,
|
|
InterpolationColorSpace::Srgba,
|
|
InterpolationColorSpace::Oklaba,
|
|
InterpolationColorSpace::Oklcha,
|
|
InterpolationColorSpace::OklchaLong,
|
|
InterpolationColorSpace::Hsla,
|
|
InterpolationColorSpace::HslaLong,
|
|
InterpolationColorSpace::Hsva,
|
|
InterpolationColorSpace::HsvaLong,
|
|
] {
|
|
let row = i % 18 + 1;
|
|
let column = i / 18 + 1;
|
|
i += 1;
|
|
|
|
commands.spawn((
|
|
Node {
|
|
grid_row: GridPlacement::start(row as i16 + 1),
|
|
grid_column: GridPlacement::start(column as i16 + 1),
|
|
justify_content: JustifyContent::SpaceEvenly,
|
|
..Default::default()
|
|
},
|
|
children![(
|
|
Node {
|
|
height: bevy::ui::px(30),
|
|
width: bevy::ui::px(300),
|
|
justify_content: JustifyContent::Center,
|
|
..Default::default()
|
|
},
|
|
BackgroundGradient::from(LinearGradient {
|
|
color_space,
|
|
angle: LinearGradient::TO_RIGHT,
|
|
stops: stops.clone(),
|
|
}),
|
|
children![
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
..default()
|
|
},
|
|
TextFont::from_font_size(10.),
|
|
bevy::ui::widget::Text(format!("{color_space:?}")),
|
|
]
|
|
)],
|
|
));
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
mod radial_gradient {
|
|
use bevy::color::palettes::css::RED;
|
|
use bevy::color::palettes::tailwind::GRAY_700;
|
|
use bevy::prelude::*;
|
|
use bevy::ui::ColorStop;
|
|
|
|
const CELL_SIZE: f32 = 80.;
|
|
const GAP: f32 = 10.;
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
let color_stops = vec![
|
|
ColorStop::new(Color::BLACK, px(5)),
|
|
ColorStop::new(Color::WHITE, px(5)),
|
|
ColorStop::new(Color::WHITE, percent(100)),
|
|
ColorStop::auto(RED),
|
|
];
|
|
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::RadialGradient)));
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
width: percent(100),
|
|
height: percent(100),
|
|
display: Display::Grid,
|
|
align_items: AlignItems::Start,
|
|
grid_template_columns: vec![RepeatedGridTrack::px(
|
|
GridTrackRepetition::AutoFill,
|
|
CELL_SIZE,
|
|
)],
|
|
grid_auto_flow: GridAutoFlow::Row,
|
|
row_gap: px(GAP),
|
|
column_gap: px(GAP),
|
|
padding: UiRect::all(px(GAP)),
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::RadialGradient),
|
|
))
|
|
.with_children(|commands| {
|
|
for (shape, shape_label) in [
|
|
(RadialGradientShape::ClosestSide, "ClosestSide"),
|
|
(RadialGradientShape::FarthestSide, "FarthestSide"),
|
|
(RadialGradientShape::Circle(percent(55)), "Circle(55%)"),
|
|
(RadialGradientShape::FarthestCorner, "FarthestCorner"),
|
|
] {
|
|
for (position, position_label) in [
|
|
(UiPosition::TOP_LEFT, "TOP_LEFT"),
|
|
(UiPosition::LEFT, "LEFT"),
|
|
(UiPosition::BOTTOM_LEFT, "BOTTOM_LEFT"),
|
|
(UiPosition::TOP, "TOP"),
|
|
(UiPosition::CENTER, "CENTER"),
|
|
(UiPosition::BOTTOM, "BOTTOM"),
|
|
(UiPosition::TOP_RIGHT, "TOP_RIGHT"),
|
|
(UiPosition::RIGHT, "RIGHT"),
|
|
(UiPosition::BOTTOM_RIGHT, "BOTTOM_RIGHT"),
|
|
] {
|
|
for (w, h) in [(CELL_SIZE, CELL_SIZE), (CELL_SIZE, CELL_SIZE / 2.)] {
|
|
commands
|
|
.spawn((
|
|
BackgroundColor(GRAY_700.into()),
|
|
Node {
|
|
display: Display::Grid,
|
|
width: px(CELL_SIZE),
|
|
..Default::default()
|
|
},
|
|
))
|
|
.with_children(|commands| {
|
|
commands.spawn((
|
|
Node {
|
|
margin: UiRect::all(px(2)),
|
|
..default()
|
|
},
|
|
Text(format!("{shape_label}\n{position_label}")),
|
|
TextFont::from_font_size(9.),
|
|
));
|
|
commands.spawn((
|
|
Node {
|
|
width: px(w),
|
|
height: px(h),
|
|
..default()
|
|
},
|
|
BackgroundGradient::from(RadialGradient {
|
|
stops: color_stops.clone(),
|
|
position,
|
|
shape,
|
|
..default()
|
|
}),
|
|
));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
mod transformations {
|
|
use bevy::{color::palettes::css::*, prelude::*};
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::Transformations)));
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
width: percent(100),
|
|
height: percent(100),
|
|
display: Display::Block,
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::Transformations),
|
|
))
|
|
.with_children(|parent| {
|
|
for (transformation, label, background) in [
|
|
(
|
|
UiTransform::from_rotation(Rot2::degrees(45.)),
|
|
"Rotate 45 degrees",
|
|
RED,
|
|
),
|
|
(
|
|
UiTransform::from_scale(Vec2::new(2., 0.5)),
|
|
"Scale 2.x 0.5y",
|
|
GREEN,
|
|
),
|
|
(
|
|
UiTransform::from_translation(Val2::px(-50., 50.)),
|
|
"Translate -50px x +50px y",
|
|
BLUE,
|
|
),
|
|
(
|
|
UiTransform {
|
|
translation: Val2::px(50., 0.),
|
|
scale: Vec2::new(-1., 1.),
|
|
rotation: Rot2::degrees(30.),
|
|
},
|
|
"T 50px x\nS -1.x (refl)\nR 30deg",
|
|
DARK_CYAN,
|
|
),
|
|
] {
|
|
parent
|
|
.spawn((Node {
|
|
width: percent(100),
|
|
margin: UiRect {
|
|
top: px(50),
|
|
bottom: px(50),
|
|
..default()
|
|
},
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::SpaceAround,
|
|
..default()
|
|
},))
|
|
.with_children(|row| {
|
|
row.spawn((
|
|
Text::new("Before Tf"),
|
|
Node {
|
|
width: px(100),
|
|
height: px(100),
|
|
border_radius: BorderRadius::bottom_right(px(25.)),
|
|
..default()
|
|
},
|
|
BackgroundColor(background.into()),
|
|
TextFont::default(),
|
|
));
|
|
row.spawn((
|
|
Text::new(label),
|
|
Node {
|
|
width: px(100),
|
|
height: px(100),
|
|
border_radius: BorderRadius::bottom_right(px(25.)),
|
|
..default()
|
|
},
|
|
BackgroundColor(background.into()),
|
|
transformation,
|
|
TextFont::default(),
|
|
));
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "bevy_ui_debug")]
|
|
mod debug_outlines {
|
|
use bevy::{
|
|
color::palettes::css::{BLUE, GRAY, RED},
|
|
prelude::*,
|
|
ui_render::UiDebugOptions,
|
|
};
|
|
|
|
pub fn setup(mut commands: Commands, mut debug_options: ResMut<GlobalUiDebugOptions>) {
|
|
debug_options.enabled = true;
|
|
debug_options.line_width = 5.;
|
|
debug_options.line_color_override = Some(LinearRgba::GREEN);
|
|
debug_options.show_hidden = true;
|
|
debug_options.show_clipped = true;
|
|
|
|
let debug_options: UiDebugOptions = (*debug_options.as_ref()).into();
|
|
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::DebugOutlines)));
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
width: percent(100),
|
|
height: percent(50),
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::SpaceAround,
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::DebugOutlines),
|
|
))
|
|
.with_children(|parent| {
|
|
parent.spawn((
|
|
Node {
|
|
width: px(100),
|
|
height: px(100),
|
|
..default()
|
|
},
|
|
BackgroundColor(GRAY.into()),
|
|
UiTransform::from_rotation(Rot2::degrees(45.)),
|
|
));
|
|
|
|
parent.spawn((Text::new("Regular Text"), TextFont::default()));
|
|
|
|
parent.spawn((
|
|
Node {
|
|
width: px(100),
|
|
height: px(100),
|
|
..default()
|
|
},
|
|
Text::new("Invisible"),
|
|
BackgroundColor(GRAY.into()),
|
|
TextFont::default(),
|
|
Visibility::Hidden,
|
|
));
|
|
|
|
parent
|
|
.spawn((
|
|
Node {
|
|
width: px(100),
|
|
height: px(100),
|
|
padding: UiRect {
|
|
left: px(25),
|
|
top: px(25),
|
|
..Default::default()
|
|
},
|
|
overflow: Overflow::clip(),
|
|
..default()
|
|
},
|
|
BackgroundColor(RED.into()),
|
|
))
|
|
.with_children(|child| {
|
|
child.spawn((
|
|
Node {
|
|
min_width: px(100),
|
|
min_height: px(100),
|
|
..default()
|
|
},
|
|
BackgroundColor(BLUE.into()),
|
|
));
|
|
});
|
|
});
|
|
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
width: percent(100),
|
|
height: percent(50),
|
|
top: percent(50),
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::SpaceAround,
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::DebugOutlines),
|
|
))
|
|
.with_children(|parent| {
|
|
parent.spawn((
|
|
Node {
|
|
width: px(200),
|
|
height: px(200),
|
|
border: UiRect {
|
|
top: px(10),
|
|
bottom: px(20),
|
|
left: px(30),
|
|
right: px(40),
|
|
},
|
|
border_radius: BorderRadius::bottom_right(px(10)),
|
|
padding: UiRect {
|
|
top: px(40),
|
|
bottom: px(30),
|
|
left: px(20),
|
|
right: px(10),
|
|
},
|
|
..default()
|
|
},
|
|
children![(
|
|
Text::new("border padding content outlines"),
|
|
TextFont::default(),
|
|
UiDebugOptions {
|
|
enabled: false,
|
|
..default()
|
|
}
|
|
)],
|
|
UiDebugOptions {
|
|
outline_border_box: true,
|
|
outline_padding_box: true,
|
|
outline_content_box: true,
|
|
ignore_border_radius: false,
|
|
..debug_options
|
|
},
|
|
));
|
|
|
|
// Vertical scrollbar (non-functional)
|
|
parent.spawn((
|
|
Node {
|
|
flex_direction: FlexDirection::Column,
|
|
width: px(90),
|
|
height: px(230),
|
|
overflow: Overflow::scroll_y(),
|
|
scrollbar_width: 20.,
|
|
..default()
|
|
},
|
|
ScrollPosition(Vec2::new(180., 180.)),
|
|
UiDebugOptions {
|
|
line_width: 3.,
|
|
outline_scrollbars: true,
|
|
show_hidden: false,
|
|
show_clipped: false,
|
|
..debug_options
|
|
},
|
|
Children::spawn(SpawnIter((0..20).map(move |i| {
|
|
(
|
|
Node::default(),
|
|
children![(
|
|
Text(format!("Item {i}")),
|
|
UiDebugOptions {
|
|
enabled: false,
|
|
..default()
|
|
}
|
|
)],
|
|
UiDebugOptions {
|
|
enabled: false,
|
|
..default()
|
|
},
|
|
)
|
|
}))),
|
|
));
|
|
|
|
// Horizontal scrollbar (non-functional)
|
|
parent.spawn((
|
|
Node {
|
|
flex_direction: FlexDirection::Row,
|
|
width: px(156),
|
|
height: px(70),
|
|
overflow: Overflow::scroll_x(),
|
|
scrollbar_width: 10.,
|
|
..default()
|
|
},
|
|
UiDebugOptions {
|
|
line_width: 3.,
|
|
outline_scrollbars: true,
|
|
show_hidden: false,
|
|
show_clipped: false,
|
|
..debug_options
|
|
},
|
|
Children::spawn(SpawnIter((0..20).map(move |i| {
|
|
(
|
|
Node::default(),
|
|
children![(
|
|
Text(format!("Item {i}")),
|
|
UiDebugOptions {
|
|
enabled: false,
|
|
..default()
|
|
}
|
|
)],
|
|
UiDebugOptions {
|
|
enabled: false,
|
|
..default()
|
|
},
|
|
)
|
|
}))),
|
|
));
|
|
|
|
// bi-directional scrollbar (non-functional)
|
|
parent.spawn((
|
|
Node {
|
|
flex_direction: FlexDirection::Column,
|
|
width: px(230),
|
|
height: px(125),
|
|
overflow: Overflow::scroll(),
|
|
scrollbar_width: 20.,
|
|
..default()
|
|
},
|
|
ScrollPosition(Vec2::new(300., 0.)),
|
|
UiDebugOptions {
|
|
line_width: 3.,
|
|
outline_scrollbars: true,
|
|
show_hidden: false,
|
|
show_clipped: false,
|
|
..debug_options
|
|
},
|
|
Children::spawn(SpawnIter((0..6).map(move |i| {
|
|
(
|
|
Node {
|
|
flex_direction: FlexDirection::Row,
|
|
..default()
|
|
},
|
|
Children::spawn(SpawnIter((0..6).map({
|
|
move |j| {
|
|
(
|
|
Text(format!("Item {}", (i * 5) + j)),
|
|
UiDebugOptions {
|
|
enabled: false,
|
|
..default()
|
|
},
|
|
)
|
|
}
|
|
}))),
|
|
UiDebugOptions {
|
|
enabled: false,
|
|
..default()
|
|
},
|
|
)
|
|
}))),
|
|
));
|
|
});
|
|
}
|
|
|
|
pub fn teardown(mut debug_options: ResMut<GlobalUiDebugOptions>) {
|
|
*debug_options = GlobalUiDebugOptions::default();
|
|
}
|
|
}
|
|
|
|
mod viewport_coords {
|
|
use bevy::{color::palettes::css::*, prelude::*};
|
|
|
|
const PALETTE: [Srgba; 9] = [RED, WHITE, BEIGE, AQUA, CRIMSON, NAVY, AZURE, LIME, BLACK];
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::ViewportCoords)));
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
width: vw(100),
|
|
height: vh(100),
|
|
border: UiRect::axes(vw(5), vh(5)),
|
|
flex_wrap: FlexWrap::Wrap,
|
|
..default()
|
|
},
|
|
BorderColor::all(PALETTE[0]),
|
|
DespawnOnExit(super::Scene::ViewportCoords),
|
|
))
|
|
.with_children(|builder| {
|
|
builder.spawn((
|
|
Node {
|
|
width: vw(30),
|
|
height: vh(30),
|
|
border: UiRect::all(vmin(5)),
|
|
..default()
|
|
},
|
|
BackgroundColor(PALETTE[1].into()),
|
|
BorderColor::all(PALETTE[8]),
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
width: vw(60),
|
|
height: vh(30),
|
|
..default()
|
|
},
|
|
BackgroundColor(PALETTE[2].into()),
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
width: vw(45),
|
|
height: vh(30),
|
|
border: UiRect::left(vmax(45. / 2.)),
|
|
..default()
|
|
},
|
|
BackgroundColor(PALETTE[3].into()),
|
|
BorderColor::all(PALETTE[7]),
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
width: vw(45),
|
|
height: vh(30),
|
|
border: UiRect::right(vmax(45. / 2.)),
|
|
..default()
|
|
},
|
|
BackgroundColor(PALETTE[4].into()),
|
|
BorderColor::all(PALETTE[7]),
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
width: vw(60),
|
|
height: vh(30),
|
|
..default()
|
|
},
|
|
BackgroundColor(PALETTE[5].into()),
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
width: vw(30),
|
|
height: vh(30),
|
|
border: UiRect::all(vmin(5)),
|
|
..default()
|
|
},
|
|
BackgroundColor(PALETTE[6].into()),
|
|
BorderColor::all(PALETTE[8]),
|
|
));
|
|
});
|
|
}
|
|
}
|
|
|
|
mod outer_color {
|
|
use bevy::prelude::*;
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
let radius = percent(33.);
|
|
let width = px(10.);
|
|
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::OuterColor)));
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
display: Display::Grid,
|
|
grid_template_columns: RepeatedGridTrack::px(3, 200.),
|
|
grid_template_rows: RepeatedGridTrack::px(3, 200.),
|
|
margin: UiRect::AUTO,
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::OuterColor),
|
|
))
|
|
.with_children(|builder| {
|
|
for (border, border_radius, invert) in [
|
|
(UiRect::ZERO, BorderRadius::bottom_right(radius), true),
|
|
(UiRect::top(width), BorderRadius::top(radius), false),
|
|
(UiRect::ZERO, BorderRadius::bottom_left(radius), true),
|
|
(UiRect::left(width), BorderRadius::left(radius), false),
|
|
(UiRect::all(width), BorderRadius::all(radius), true),
|
|
(UiRect::right(width), BorderRadius::right(radius), false),
|
|
(UiRect::ZERO, BorderRadius::top_right(radius), true),
|
|
(UiRect::bottom(width), BorderRadius::bottom(radius), false),
|
|
(UiRect::ZERO, BorderRadius::top_left(radius), true),
|
|
] {
|
|
builder
|
|
.spawn((
|
|
Node {
|
|
width: px(200.),
|
|
height: px(200.),
|
|
border_radius,
|
|
border,
|
|
..default()
|
|
},
|
|
BorderColor::all(bevy::color::palettes::css::RED),
|
|
))
|
|
.insert_if(BackgroundColor(Color::WHITE), || !invert)
|
|
.insert_if(OuterColor(Color::WHITE), || invert);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
mod boxed_content {
|
|
use bevy::color::palettes::css::RED;
|
|
use bevy::prelude::*;
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::BoxedContent)));
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
margin: auto().all(),
|
|
column_gap: px(30),
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::BoxedContent),
|
|
))
|
|
.with_children(|builder| {
|
|
for (heading, text_justify) in [
|
|
("Left", Justify::Left),
|
|
("Center", Justify::Center),
|
|
("Right", Justify::Right),
|
|
] {
|
|
builder
|
|
.spawn(Node {
|
|
flex_direction: FlexDirection::Column,
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Start,
|
|
row_gap: px(20),
|
|
..default()
|
|
})
|
|
.with_children(|builder| {
|
|
builder.spawn((
|
|
Node::default(),
|
|
Text::new(format!("{heading} justify")),
|
|
TextFont::from_font_size(FontSize::Px(14.)),
|
|
TextLayout::justify(Justify::Center),
|
|
));
|
|
|
|
builder.spawn((
|
|
Node::default(),
|
|
Text::new("This text has\nno border or padding."),
|
|
TextFont::from_font_size(FontSize::Px(10.)),
|
|
TextLayout::justify(text_justify),
|
|
Outline {
|
|
width: px(2),
|
|
color: Color::WHITE,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
border: px(10).all(),
|
|
..default()
|
|
},
|
|
Text::new("This text has\na border but no padding."),
|
|
TextFont::from_font_size(FontSize::Px(10.)),
|
|
TextLayout::justify(text_justify),
|
|
BorderColor::all(RED),
|
|
Outline {
|
|
width: px(2),
|
|
color: Color::WHITE,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
padding: px(20).all(),
|
|
..default()
|
|
},
|
|
Text::new("This text has\npadding but no border."),
|
|
TextFont::from_font_size(FontSize::Px(10.)),
|
|
TextLayout::justify(text_justify),
|
|
Outline {
|
|
width: px(2),
|
|
color: Color::WHITE,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
border: px(10).all(),
|
|
padding: px(20).all(),
|
|
..default()
|
|
},
|
|
Text::new("This text has\nborder and padding."),
|
|
TextFont::from_font_size(FontSize::Px(10.)),
|
|
TextLayout::justify(text_justify),
|
|
BorderColor::all(RED),
|
|
Outline {
|
|
width: px(2),
|
|
color: Color::WHITE,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
border: px(10).left(),
|
|
..default()
|
|
},
|
|
Text::new("This text has\na left border and no padding."),
|
|
TextFont::from_font_size(FontSize::Px(10.)),
|
|
TextLayout::justify(text_justify),
|
|
BorderColor::all(RED),
|
|
Outline {
|
|
width: px(2),
|
|
color: Color::WHITE,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
border: px(10).right(),
|
|
..default()
|
|
},
|
|
Text::new("This text has\na right border and no padding."),
|
|
TextFont::from_font_size(FontSize::Px(10.)),
|
|
TextLayout::justify(text_justify),
|
|
BorderColor::all(RED),
|
|
Outline {
|
|
width: px(2),
|
|
color: Color::WHITE,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
padding: px(20).top().with_right(px(20)),
|
|
..default()
|
|
},
|
|
Text::new("This text has\npadding on its top and right."),
|
|
TextFont::from_font_size(FontSize::Px(10.)),
|
|
TextLayout::justify(text_justify),
|
|
BorderColor::all(RED),
|
|
Outline {
|
|
width: px(2),
|
|
color: Color::WHITE,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
padding: px(20).bottom().with_left(px(20)),
|
|
..default()
|
|
},
|
|
Text::new("This text has\npadding on its bottom and left."),
|
|
TextFont::from_font_size(FontSize::Px(10.)),
|
|
TextLayout::justify(text_justify),
|
|
BorderColor::all(RED),
|
|
Outline {
|
|
width: px(2),
|
|
color: Color::WHITE,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
builder.spawn((
|
|
Node {
|
|
padding: px(20).top().with_left(px(20)),
|
|
border: px(10).bottom().with_right(px(10)),
|
|
..default()
|
|
},
|
|
Text::new(
|
|
"This text has\npadding on its top and left\nand a border on its bottom and right.",
|
|
),
|
|
TextFont::from_font_size(FontSize::Px(10.)),
|
|
TextLayout::justify(text_justify),
|
|
BorderColor::all(RED),
|
|
Outline {
|
|
width: px(2),
|
|
color: Color::WHITE,
|
|
..Default::default()
|
|
},
|
|
));
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
mod editable_text {
|
|
use bevy::color::palettes::css::YELLOW;
|
|
use bevy::prelude::*;
|
|
use bevy::text::EditableText;
|
|
use bevy::text::TextEdit;
|
|
use bevy::ui::widget::TextScroll;
|
|
|
|
pub fn setup(mut commands: Commands) {
|
|
commands.spawn((Camera2d, DespawnOnExit(super::Scene::EditableText)));
|
|
commands.spawn((
|
|
Node {
|
|
flex_direction: FlexDirection::Column,
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
width: vw(100),
|
|
height: vh(100),
|
|
row_gap: px(25.),
|
|
..default()
|
|
},
|
|
DespawnOnExit(super::Scene::EditableText),
|
|
children![
|
|
(
|
|
EditableText {
|
|
pending_edits: vec![TextEdit::Insert("Single line EditableText".into())],
|
|
..default()
|
|
},
|
|
Node {
|
|
width: px(200.),
|
|
border: px(2).all(),
|
|
..default()
|
|
},
|
|
BorderColor::all(YELLOW),
|
|
),
|
|
(
|
|
EditableText {
|
|
pending_edits: vec![
|
|
TextEdit::Insert(
|
|
"1. Multiline EditableText\n2.\n3.\n4.\n5.\n6.\n7.\n8.\n9.\n10."
|
|
.into()
|
|
),
|
|
TextEdit::TextStart(false),
|
|
],
|
|
visible_lines: Some(8.),
|
|
..default()
|
|
},
|
|
TextScroll::default(),
|
|
Node {
|
|
width: px(350.),
|
|
border: px(2).all(),
|
|
..default()
|
|
},
|
|
BorderColor::all(YELLOW),
|
|
),
|
|
(
|
|
EditableText {
|
|
pending_edits: vec![
|
|
TextEdit::Insert(
|
|
"1. Multiline EditableText\n2.\n3.\n4.\n5.\n6.\n7.\n8.\n9.\n10."
|
|
.into()
|
|
),
|
|
TextEdit::TextEnd(true),
|
|
],
|
|
visible_lines: Some(8.),
|
|
..default()
|
|
},
|
|
TextScroll::default(),
|
|
Node {
|
|
width: px(350.),
|
|
border: px(2).all(),
|
|
..default()
|
|
},
|
|
BorderColor::all(YELLOW),
|
|
),
|
|
],
|
|
));
|
|
}
|
|
}
|