Created
April 25, 2023 15:10
-
-
Save tigregalis/51d0b08426da5c53779bcbb944a8e071 to your computer and use it in GitHub Desktop.
bevy-style-stack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "bevy-style-stack" | |
version = "0.1.0" | |
edition = "2021" | |
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
[dependencies.bevy] | |
git = "https://github.com/bevyengine/bevy" | |
rev = "6f291a0" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use bevy::prelude::*; | |
// -- application -- | |
fn main() { | |
App::new() | |
.add_plugins(DefaultPlugins) | |
.add_plugin(StyleStackPlugin) | |
.add_systems(Startup, setup) | |
.add_systems(PreUpdate, toggle_styles) | |
.run(); | |
} | |
#[derive(Resource)] | |
struct MyStyles { | |
base_style: StyleLayerEntity, | |
hidden_style: StyleLayerEntity, | |
bg_style: StyleLayerEntity, | |
} | |
#[derive(Component)] | |
struct Index(usize); | |
#[derive(Component)] | |
struct SelectedIndexText; | |
#[derive(Component)] | |
struct ToggleVisibility; | |
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { | |
// set up camera | |
commands.spawn(Camera2dBundle::default()); | |
// set up styles | |
let base_style = commands | |
.spawn(StyleLayer { | |
display: Some(Display::Flex), | |
background_color: Some(BackgroundColor(Color::BLACK)), | |
..default() | |
}) | |
.id(); | |
let hidden_style = commands | |
.spawn(StyleLayer { | |
display: Some(Display::None), | |
..default() | |
}) | |
.id(); | |
let bg_style = commands | |
.spawn(StyleLayer { | |
background_color: Some(BackgroundColor(Color::GREEN)), | |
..default() | |
}) | |
.id(); | |
commands.insert_resource(MyStyles { | |
base_style, | |
hidden_style, | |
bg_style, | |
}); | |
// set up text style | |
let text_style = TextStyle { | |
font: asset_server.load("fonts/FiraSans-Bold.ttf"), | |
font_size: 40.0, | |
color: Color::WHITE, | |
}; | |
// spawn a bunch of entities | |
commands | |
.spawn(NodeBundle { | |
style: Style { | |
display: Display::Flex, | |
flex_direction: FlexDirection::Column, | |
justify_items: JustifyItems::Center, | |
align_items: AlignItems::Center, | |
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), | |
..default() | |
}, | |
..default() | |
}) | |
.with_children(|parent| { | |
parent.spawn((TextBundle { | |
text: Text { | |
sections: vec![ | |
TextSection::new( | |
"Use arrows or numbers to make a selection.\nPress space or enter to toggle visibility.\nCurrent selection: ", | |
text_style.clone(), | |
), | |
TextSection::new("0", text_style.clone()), | |
], | |
..default() | |
}, | |
..default() | |
}, SelectedIndexText)); | |
for i in 0..10 { | |
parent | |
.spawn(( | |
Index(i), | |
StyleStack(if i == 0 { | |
vec![base_style, bg_style] | |
} else { | |
vec![base_style] | |
}), | |
NodeBundle { | |
style: Style { | |
size: Size::new(Val::Px(100.0), Val::Percent(100.)), | |
// horizontally center child text | |
justify_content: JustifyContent::Center, | |
// vertically center child text | |
align_items: AlignItems::Center, | |
..default() | |
}, | |
..default() | |
}, | |
ToggleVisibility, | |
)) | |
.with_children(|parent| { | |
parent.spawn(TextBundle { | |
text: Text::from_section( | |
format!("{i}"), | |
text_style.clone(), | |
), | |
..default() | |
}); | |
}); | |
} | |
}); | |
} | |
fn toggle_styles( | |
mut selected: Local<usize>, | |
keys: Res<Input<KeyCode>>, | |
mut query: Query<(&Index, &mut StyleStack), With<ToggleVisibility>>, | |
mut selected_index_text: Query<&mut Text, With<SelectedIndexText>>, | |
styles: Res<MyStyles>, | |
) { | |
for key in keys.get_just_pressed() { | |
let should_update_selection = match key { | |
// use space or enter to toggle Display::None | |
// (base style with Display::Flex still applies regardless) | |
KeyCode::Space | KeyCode::NumpadEnter | KeyCode::Return => { | |
if let Some((_, mut style_stack)) = | |
query.iter_mut().find(|(index, _)| index.0 == *selected) | |
{ | |
style_stack.toggle(styles.hidden_style); | |
bevy::log::info!("toggled hidden style"); | |
} | |
None | |
} | |
// use numbers or arrow keys to select | |
KeyCode::Key0 | KeyCode::Numpad0 => Some(0), | |
KeyCode::Key1 | KeyCode::Numpad1 => Some(1), | |
KeyCode::Key2 | KeyCode::Numpad2 => Some(2), | |
KeyCode::Key3 | KeyCode::Numpad3 => Some(3), | |
KeyCode::Key4 | KeyCode::Numpad4 => Some(4), | |
KeyCode::Key5 | KeyCode::Numpad5 => Some(5), | |
KeyCode::Key6 | KeyCode::Numpad6 => Some(6), | |
KeyCode::Key7 | KeyCode::Numpad7 => Some(7), | |
KeyCode::Key8 | KeyCode::Numpad8 => Some(8), | |
KeyCode::Key9 | KeyCode::Numpad9 => Some(9), | |
KeyCode::Up => Some(selected.max(1) - 1), | |
KeyCode::Down => Some(selected.min(8) + 1), | |
_ => None, | |
}; | |
if let Some(new_selection) = should_update_selection { | |
*selected = new_selection; | |
for (index, mut style_stack) in query.iter_mut() { | |
if index.0 == *selected { | |
style_stack.add(styles.bg_style); | |
bevy::log::info!("added background style"); | |
} else { | |
style_stack.remove(styles.bg_style); | |
} | |
} | |
selected_index_text.single_mut().sections[1].value = format!("{}", *selected); | |
} | |
} | |
} | |
// -- plugin -- | |
struct StyleStackPlugin; | |
impl Plugin for StyleStackPlugin { | |
fn build(&self, app: &mut App) { | |
app.add_systems(PostUpdate, update_styles); | |
} | |
} | |
fn update_styles( | |
mut query: Query<(&mut Style, &mut BackgroundColor, Ref<StyleStack>), Without<StyleLayer>>, | |
styles: Query<&StyleLayer>, | |
) { | |
for (mut style, mut bg_color, style_stack) in query.iter_mut() { | |
if style_stack.is_changed() { | |
// TODO: handle when the reference style layer itself is changed | |
*bg_color = style_stack | |
.0 | |
.iter() | |
.rev() | |
.find_map(|style_entity| { | |
styles | |
.get(*style_entity) | |
.ok() | |
.and_then(|style| style.background_color) | |
}) | |
.unwrap_or(BackgroundColor::DEFAULT); | |
style.display = style_stack | |
.0 | |
.iter() | |
.rev() | |
.find_map(|style_entity| { | |
styles | |
.get(*style_entity) | |
.ok() | |
.and_then(|style| style.display) | |
}) | |
.unwrap_or(Style::DEFAULT.display); | |
// TODO: handle other style properties | |
} | |
} | |
} | |
type StyleLayerEntity = Entity; | |
/// A stack of [`StyleLayerEntity`]s. | |
/// | |
/// Similar to the browser [Element: classList](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList) API. | |
#[derive(Component, Default, Debug)] | |
struct StyleStack(Vec<StyleLayerEntity>); | |
impl StyleStack { | |
/// add a [`StyleLayerEntity`] on top of the stack | |
/// | |
/// first removes it from the stack if already present, so that the newly added entity is on top | |
fn add(&mut self, entity: StyleLayerEntity) { | |
self.remove(entity); | |
self.0.push(entity); | |
} | |
/// remove a [`StyleLayerEntity`] if present | |
fn remove(&mut self, entity: StyleLayerEntity) { | |
self.0.retain(|e| e != &entity); | |
} | |
/// replace a [`StyleLayerEntity`] if present | |
/// | |
/// does not replace if not already present | |
fn replace(&mut self, old_entity: StyleLayerEntity, new_entity: StyleLayerEntity) { | |
let Some(pos) = self.0 | |
.iter() | |
.position(|e| *e == old_entity) else { | |
return; | |
}; | |
self.0[pos] = new_entity; | |
} | |
/// toggle a [`StyleLayerEntity`] | |
fn toggle(&mut self, entity: StyleLayerEntity) { | |
if let Some(pos) = self.0.iter().position(|e| *e == entity) { | |
self.0.remove(pos); | |
} else { | |
self.add(entity); | |
} | |
} | |
/// clear the stack | |
fn clear(&mut self) { | |
self.0.clear(); | |
} | |
} | |
#[derive(Component, Clone, Debug, Default)] | |
pub struct StyleLayer { | |
/// The background color of the node | |
/// | |
/// This serves as the "fill" color. | |
/// When combined with [`UiImage`], tints the provided texture. | |
pub background_color: Option<BackgroundColor>, | |
/// Which layout algorithm to use when laying out this node's contents: | |
/// - [`Display::Flex`]: Use the Flexbox layout algorithm | |
/// - [`Display::Grid`]: Use the CSS Grid layout algorithm | |
/// - [`Display::None`]: Hide this node and perform layout as if it does not exist. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/display> | |
pub display: Option<Display>, | |
/// Whether a node should be laid out in-flow with, or independently of it's siblings: | |
/// - [`PositionType::Relative`]: Layout this node in-flow with other nodes using the usual (flexbox/grid) layout algorithm. | |
/// - [`PositionType::Absolute`]: Layout this node on top and independently of other nodes. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/position> | |
pub position_type: Option<PositionType>, | |
/// Whether overflowing content should be displayed or clipped. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow> | |
pub overflow: Option<Overflow>, | |
/// Defines the text direction. For example English is written LTR (left-to-right) while Arabic is written RTL (right-to-left). | |
/// | |
/// Note: the corresponding CSS property also affects box layout order, but this isn't yet implemented in bevy. | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/direction> | |
pub direction: Option<Direction>, | |
/// The horizontal position of the left edge of the node. | |
/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout. | |
/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/left> | |
pub left: Option<Val>, | |
/// The horizontal position of the right edge of the node. | |
/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout. | |
/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/right> | |
pub right: Option<Val>, | |
/// The vertical position of the top edge of the node. | |
/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout. | |
/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/top> | |
pub top: Option<Val>, | |
/// The vertical position of the bottom edge of the node. | |
/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout. | |
/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/bottom> | |
pub bottom: Option<Val>, | |
/// The ideal size of the node | |
/// | |
/// `size.width` is used when it is within the bounds defined by `min_size.width` and `max_size.width`. | |
/// `size.height` is used when it is within the bounds defined by `min_size.height` and `max_size.height`. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/width> <br /> | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/height> | |
pub size: Option<Size>, | |
/// The minimum size of the node | |
/// | |
/// `min_size.width` is used if it is greater than either `size.width` or `max_size.width`, or both. | |
/// `min_size.height` is used if it is greater than either `size.height` or `max_size.height`, or both. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-width> <br /> | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-height> | |
pub min_size: Option<Size>, | |
/// The maximum size of the node | |
/// | |
/// `max_size.width` is used if it is within the bounds defined by `min_size.width` and `size.width`. | |
/// `max_size.height` is used if it is within the bounds defined by `min_size.height` and `size.height. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-width> <br /> | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-height> | |
pub max_size: Option<Size>, | |
/// The aspect ratio of the node (defined as `width / height`) | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio> | |
pub aspect_ratio: Option<Option<f32>>, | |
/// For Flexbox containers: | |
/// - Sets default cross-axis alignment of the child items. | |
/// For CSS Grid containers: | |
/// - Controls block (vertical) axis alignment of children of this grid container within their grid areas | |
/// | |
/// This value is overriden [`JustifySelf`] on the child node is set. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-items> | |
pub align_items: Option<AlignItems>, | |
/// For Flexbox containers: | |
/// - This property has no effect. See `justify_content` for main-axis alignment of flex items. | |
/// For CSS Grid containers: | |
/// - Sets default inline (horizontal) axis alignment of child items within their grid areas | |
/// | |
/// This value is overriden [`JustifySelf`] on the child node is set. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items> | |
pub justify_items: Option<JustifyItems>, | |
/// For Flexbox items: | |
/// - Controls cross-axis alignment of the item. | |
/// For CSS Grid items: | |
/// - Controls block (vertical) axis alignment of a grid item within it's grid area | |
/// | |
/// If set to `Auto`, alignment is inherited from the value of [`AlignItems`] set on the parent node. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-self> | |
pub align_self: Option<AlignSelf>, | |
/// For Flexbox items: | |
/// - This property has no effect. See `justify_content` for main-axis alignment of flex items. | |
/// For CSS Grid items: | |
/// - Controls inline (horizontal) axis alignment of a grid item within it's grid area. | |
/// | |
/// If set to `Auto`, alignment is inherited from the value of [`JustifyItems`] set on the parent node. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items> | |
pub justify_self: Option<JustifySelf>, | |
/// For Flexbox containers: | |
/// - Controls alignment of lines if flex_wrap is set to [`FlexWrap::Wrap`] and there are multiple lines of items | |
/// For CSS Grid container: | |
/// - Controls alignment of grid rows | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-content> | |
pub align_content: Option<AlignContent>, | |
/// For Flexbox containers: | |
/// - Controls alignment of items in the main axis | |
/// For CSS Grid containers: | |
/// - Controls alignment of grid columns | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content> | |
pub justify_content: Option<JustifyContent>, | |
/// The amount of space around a node outside its border. | |
/// | |
/// If a percentage value is used, the percentage is calculated based on the width of the parent node. | |
/// | |
/// # Example | |
/// ``` | |
/// # use bevy_ui::{Style, UiRect, Val}; | |
/// let style = Style { | |
/// margin: UiRect { | |
/// left: Val::Percent(10.), | |
/// right: Val::Percent(10.), | |
/// top: Val::Percent(15.), | |
/// bottom: Val::Percent(15.) | |
/// }, | |
/// ..Default::default() | |
/// }; | |
/// ``` | |
/// A node with this style and a parent with dimensions of 100px by 300px, will have calculated margins of 10px on both left and right edges, and 15px on both top and bottom edges. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/margin> | |
pub margin: Option<UiRect>, | |
/// The amount of space between the edges of a node and its contents. | |
/// | |
/// If a percentage value is used, the percentage is calculated based on the width of the parent node. | |
/// | |
/// # Example | |
/// ``` | |
/// # use bevy_ui::{Style, UiRect, Val}; | |
/// let style = Style { | |
/// padding: UiRect { | |
/// left: Val::Percent(1.), | |
/// right: Val::Percent(2.), | |
/// top: Val::Percent(3.), | |
/// bottom: Val::Percent(4.) | |
/// }, | |
/// ..Default::default() | |
/// }; | |
/// ``` | |
/// A node with this style and a parent with dimensions of 300px by 100px, will have calculated padding of 3px on the left, 6px on the right, 9px on the top and 12px on the bottom. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/padding> | |
pub padding: Option<UiRect>, | |
/// The amount of space between the margins of a node and its padding. | |
/// | |
/// If a percentage value is used, the percentage is calculated based on the width of the parent node. | |
/// | |
/// The size of the node will be expanded if there are constraints that prevent the layout algorithm from placing the border within the existing node boundary. | |
/// | |
/// Rendering for borders is not yet implemented. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width> | |
pub border: Option<UiRect>, | |
/// Whether a Flexbox container should be a row or a column. This property has no effect of Grid nodes. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction> | |
pub flex_direction: Option<FlexDirection>, | |
/// Whether a Flexbox container should wrap it's contents onto multiple line wrap if they overflow. This property has no effect of Grid nodes. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap> | |
pub flex_wrap: Option<FlexWrap>, | |
/// Defines how much a flexbox item should grow if there's space available. Defaults to 0 (don't grow at all). | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow> | |
pub flex_grow: Option<f32>, | |
/// Defines how much a flexbox item should shrink if there's not enough space available. Defaults to 1. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink> | |
pub flex_shrink: Option<f32>, | |
/// The initial length of a flexbox in the main axis, before flex growing/shrinking properties are applied. | |
/// | |
/// `flex_basis` overrides `size` on the main axis if both are set, but it obeys the bounds defined by `min_size` and `max_size`. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis> | |
pub flex_basis: Option<Val>, | |
/// The size of the gutters between items in flexbox layout or rows/columns in a grid layout | |
/// | |
/// Note: Values of `Val::Auto` are not valid and are treated as zero. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gap> | |
pub gap: Option<Size>, | |
/// Controls whether automatically placed grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used. | |
/// Only affect Grid layouts | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow> | |
pub grid_auto_flow: Option<GridAutoFlow>, | |
/// Defines the number of rows a grid has and the sizes of those rows. If grid items are given explicit placements then more rows may | |
/// be implicitly generated by items that are placed out of bounds. The sizes of those rows are controlled by `grid_auto_rows` property. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-rows> | |
pub grid_template_rows: Option<Vec<RepeatedGridTrack>>, | |
/// Defines the number of columns a grid has and the sizes of those columns. If grid items are given explicit placements then more columns may | |
/// be implicitly generated by items that are placed out of bounds. The sizes of those columns are controlled by `grid_auto_columns` property. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns> | |
pub grid_template_columns: Option<Vec<RepeatedGridTrack>>, | |
/// Defines the size of implicitly created rows. Rows are created implicitly when grid items are given explicit placements that are out of bounds | |
/// of the rows explicitly created using `grid_template_rows`. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-rows> | |
pub grid_auto_rows: Option<Vec<GridTrack>>, | |
/// Defines the size of implicitly created columns. Columns are created implicitly when grid items are given explicit placements that are out of bounds | |
/// of the columns explicitly created using `grid_template_columms`. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns> | |
pub grid_auto_columns: Option<Vec<GridTrack>>, | |
/// The row in which a grid item starts and how many rows it spans. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row> | |
pub grid_row: Option<GridPlacement>, | |
/// The column in which a grid item starts and how many columns it spans. | |
/// | |
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column> | |
pub grid_column: Option<GridPlacement>, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment