Theme example
CSS
Dark theme
#ui-root {
background-color: #181414
}
#left-border {
margin: 0px;
border: 0px;
padding: 5px;
background-color: #353535;
}
#ui-root #left-bg {
display: flex;
background-color: #242424;
}
#right-border {
background-color: #242424;
}
#left-bg text {
color: #b3b3b3
}
#right-list {
height: 50%;
background-color: #3b3b3b;
}
.big-text {
font-size: 15;
width: 100%;
color: #7c7c7c
}
.big-text-red {
font-size: 15;
width: 100%;
color: red;
}
text {
color: blue;
}
#mid-blue-border {
background-color: #0a0a24;
}
#mid-blue-border node {
background-color: #383838;
}
title {
font-size: 22;
vertical-align: bottom;
color: #b3b3b3;
}
.container .blue-bg {
background-color: #ff00FF10;
}
#mid-bevy-logo-image {
background-color: #aaaaaa;
}
button {
align-items: center;
align-content: center;
}
button text {
width: 100%;
height: 25px;
left: 10px;
font-size: 20;
color: azure;
text-content: "Darkness!";
}
button:hover text {
text-content: "Switch to Lightness?";
}
Light theme
#ui-root {
background-color: #bbb6b6
}
#left-border {
margin: 0px;
border: 0px;
padding: 5px;
background-color: #dddddd;
}
#ui-root #left-bg {
display: flex;
background-color: darkgray;
}
#right-border {
background-color: #a5a1a1;
}
#left-bg text {
color: #252525
}
#right-list {
height: 50%;
background-color: #ebebeb;
}
.big-text {
font-size: 15;
width: 100%;
color: #252525
}
#mid-blue-border {
background-color: #28288d;
}
#mid-blue-border node {
background-color: #a5a1a1;
}
title {
font-size: 22;
vertical-align: bottom;
color: #3f3f3f;
}
.container .blue-bg {
background-color: #44004410;
}
#mid-bevy-logo-image {
background-color: #424242;
}
button {
align-items: center;
align-content: center;
}
button text {
width: 100%;
height: 25px;
left: 10px;
font-size: 20;
color: #3b3f3f;
text-content: "Lightness!!!!";
}
button:hover text {
text-content: "Switch to Darkness?";
}
Code
use bevy::{prelude::*, ui::FocusPolicy};
use bevy_ecss::prelude::{
Class, EcssPlugin, RegisterComponentSelector, StyleSheet, StyleSheetAsset,
};
#[derive(Component, Debug, Default, Reflect)]
#[reflect(Component)]
struct Title;
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
canvas: Some("#bevy".to_string()),
..default()
}),
..default()
}))
.add_plugins(EcssPlugin::with_hot_reload())
.add_systems(Startup, setup)
.add_systems(Update, change_theme)
.register_component_selector::<Title>("title");
app.run();
}
#[derive(Resource)]
struct Themes {
pub root: Entity,
pub dark: Handle<StyleSheetAsset>,
pub light: Handle<StyleSheetAsset>,
}
fn change_theme(
themes: Res<Themes>,
mut styles_query: Query<&mut StyleSheet>,
interaction_query: Query<&Interaction, (Changed<Interaction>, With<Button>)>,
) {
for interaction in &interaction_query {
if let Interaction::Pressed = *interaction {
if let Ok(mut sheet) = styles_query.get_mut(themes.root) {
if sheet.handles().first() == Some(&themes.dark) {
sheet.set_handles(vec![themes.light.clone()]);
} else {
sheet.set_handles(vec![themes.dark.clone()]);
}
}
}
}
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let dark = asset_server.load("sheets/dark_theme.css");
let light = asset_server.load("sheets/light_theme.css");
// Camera
commands.spawn(Camera2dBundle::default());
// root node
let root = commands
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
justify_content: JustifyContent::SpaceBetween,
..default()
},
focus_policy: FocusPolicy::Pass,
background_color: Color::NONE.into(),
..default()
})
.insert(Name::new("ui-root"))
.insert(StyleSheet::new(dark.clone()))
.with_children(|parent| {
// left vertical fill (border)
parent
.spawn(NodeBundle {
style: Style {
width: Val::Px(200.0),
height: Val::Percent(100.0),
border: UiRect::all(Val::Px(2.0)),
..default()
},
background_color: Color::rgb(0.65, 0.65, 0.65).into(),
..default()
})
.insert(Name::new("left-border"))
.with_children(|parent| {
// left vertical fill (content)
parent
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::FlexEnd,
..default()
},
background_color: Color::rgb(0.15, 0.15, 0.15).into(),
..default()
})
.insert(Name::new("left-bg"))
.with_children(|parent| {
// text
parent
.spawn(
TextBundle::from_section(
"Text Example",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 30.0,
color: Color::WHITE,
},
)
.with_style(Style {
margin: UiRect::all(Val::Px(5.0)),
..default()
}),
)
.insert(Name::new("left-text"));
});
});
// right vertical fill
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::ColumnReverse,
justify_content: JustifyContent::Center,
width: Val::Px(200.0),
height: Val::Percent(100.0),
..default()
},
background_color: Color::rgb(0.15, 0.15, 0.15).into(),
..default()
})
.insert(Name::new("right-border"))
.with_children(|parent| {
// Title
parent
.spawn(
TextBundle::from_section(
"Scrolling list",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 25.,
color: Color::WHITE,
},
)
.with_style(Style {
width: Val::Auto,
height: Val::Px(25.),
margin: UiRect {
left: Val::Auto,
right: Val::Auto,
..default()
},
..default()
}),
)
.insert(Title)
.insert(Name::new("right-bg"));
// List with hidden overflow
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::ColumnReverse,
align_self: AlignSelf::Center,
width: Val::Percent(100.0),
height: Val::Percent(50.0),
overflow: Overflow::clip(),
..default()
},
background_color: Color::rgb(0.10, 0.10, 0.10).into(),
..default()
})
.insert(Name::new("right-list"))
.with_children(|parent| {
// Moving panel
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::ColumnReverse,
flex_grow: 1.0,
..default()
},
background_color: Color::NONE.into(),
..default()
})
.insert(Name::new("right-moving-panel"))
.with_children(|parent| {
// List items
for i in 0..30 {
parent
.spawn(
TextBundle::from_section(
format!("Item {i}"),
TextStyle {
font: asset_server
.load("fonts/FiraSans-Bold.ttf"),
font_size: 20.,
color: Color::WHITE,
},
)
.with_style(Style {
flex_shrink: 0.,
height: Val::Px(20.0),
margin: UiRect {
left: Val::Auto,
right: Val::Auto,
..default()
},
..default()
}),
)
.insert(Class::new("big-text"))
.insert(Name::new(format!("right-item-{}", i)));
}
});
});
});
// render order test: reddest in the back, whitest in the front (flex center)
parent
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
position_type: PositionType::Absolute,
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
background_color: Color::NONE.into(),
..default()
})
.insert(Name::new("mid-red-last"))
.insert(Class::new("blue-bg container"))
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
width: Val::Px(100.0),
height: Val::Px(100.0),
..default()
},
background_color: Color::rgb(1.0, 0.0, 0.0).into(),
..default()
})
.insert(Name::new("mid-red-last-but-one"))
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
width: Val::Px(100.0),
height: Val::Px(100.0),
position_type: PositionType::Absolute,
left: Val::Px(20.0),
bottom: Val::Px(20.0),
..default()
},
background_color: Color::rgb(1.0, 0.3, 0.3).into(),
..default()
})
.insert(Name::new("mid-red-center"));
parent
.spawn(NodeBundle {
style: Style {
width: Val::Px(100.0),
height: Val::Px(100.0),
position_type: PositionType::Absolute,
left: Val::Px(40.0),
bottom: Val::Px(40.0),
..default()
},
background_color: Color::rgb(1.0, 0.5, 0.5).into(),
..default()
})
.insert(Class::new("blue-bg"))
.insert(Name::new("mid-red-top-but-one"));
parent
.spawn(NodeBundle {
style: Style {
width: Val::Px(100.0),
height: Val::Px(100.0),
position_type: PositionType::Absolute,
left: Val::Px(60.0),
bottom: Val::Px(60.0),
..default()
},
background_color: Color::rgb(1.0, 0.7, 0.7).into(),
..default()
})
.insert(Name::new("mid-red-top"));
// alpha test
parent
.spawn(NodeBundle {
style: Style {
width: Val::Px(100.0),
height: Val::Px(100.0),
position_type: PositionType::Absolute,
left: Val::Px(80.0),
bottom: Val::Px(80.0),
..default()
},
background_color: Color::rgba(1.0, 0.9, 0.9, 0.4).into(),
..default()
})
.insert(Class::new("blue-bg"))
.insert(Name::new("mid-red-alpha"));
});
});
// bevy logo (flex center)
parent
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
position_type: PositionType::Absolute,
justify_content: JustifyContent::Center,
align_items: AlignItems::FlexEnd,
..default()
},
focus_policy: FocusPolicy::Pass,
background_color: Color::NONE.into(),
..default()
})
.insert(Name::new("mid-bevy-logo-bg"))
.with_children(|parent| {
// bevy logo (image)
parent
.spawn(ImageBundle {
style: Style {
width: Val::Px(500.0),
..default()
},
image: asset_server.load("branding/bevy_logo_dark_big.png").into(),
..default()
})
.insert(Name::new("mid-bevy-logo-image"));
});
// absolute positioning
parent
.spawn(NodeBundle {
style: Style {
width: Val::Px(200.0),
height: Val::Px(200.0),
position_type: PositionType::Absolute,
left: Val::Px(210.0),
bottom: Val::Px(10.0),
border: UiRect::all(Val::Px(20.0)),
..default()
},
background_color: Color::rgb(0.4, 0.4, 1.0).into(),
..default()
})
.insert(Name::new("mid-blue-border"))
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
width: Val::Px(100.0),
height: Val::Px(100.0),
..default()
},
focus_policy: FocusPolicy::Pass,
background_color: Color::rgb(0.8, 0.8, 1.0).into(),
..default()
})
.insert(Name::new("mid-navy-blue-content"))
.with_children(|parent| {
parent
.spawn(ButtonBundle {
style: Style {
width: Val::Px(100.0),
height: Val::Px(100.0),
..default()
},
..default()
})
.with_children(|parent| {
parent.spawn(TextBundle::from_section(
"Change Theme",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 40.0,
color: Color::rgb(0.9, 0.9, 0.9),
},
));
});
});
});
})
.id();
commands.insert_resource(Themes { root, dark, light })
}