bevy/examples/ui/ui.rs
davier 340957994d Implement the Overflow::Hidden style property for UI (#3296)
# Objective

This PR implements the `overflow` style property in `bevy_ui`. When set to `Overflow::Hidden`, the children of that node are clipped so that overflowing parts are not rendered. This is an important building block for UI widgets.

## Solution

Clipping is done on the CPU so that it does not break batching.

The clip regions update was implemented as a separate system for clarity, but it could be merged with the other UI systems to avoid doing an additional tree traversal. (I don't think it's important until we fix the layout performance issues though).

A scrolling list was added to the `ui_pipelined` example to showcase `Overflow::Hidden`. For the sake of simplicity, it can only be scrolled with a mouse.
2021-12-19 05:44:28 +00:00

330 lines
15 KiB
Rust

use bevy::{
input::mouse::{MouseScrollUnit, MouseWheel},
prelude::*,
};
/// This example illustrates the various features of Bevy UI.
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_system(mouse_scroll)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// ui camera
commands.spawn_bundle(UiCameraBundle::default());
// root node
commands
.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
justify_content: JustifyContent::SpaceBetween,
..Default::default()
},
color: Color::NONE.into(),
..Default::default()
})
.with_children(|parent| {
// left vertical fill (border)
parent
.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Px(200.0), Val::Percent(100.0)),
border: Rect::all(Val::Px(2.0)),
..Default::default()
},
color: Color::rgb(0.65, 0.65, 0.65).into(),
..Default::default()
})
.with_children(|parent| {
// left vertical fill (content)
parent
.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
align_items: AlignItems::FlexEnd,
..Default::default()
},
color: Color::rgb(0.15, 0.15, 0.15).into(),
..Default::default()
})
.with_children(|parent| {
// text
parent.spawn_bundle(TextBundle {
style: Style {
margin: Rect::all(Val::Px(5.0)),
..Default::default()
},
text: Text::with_section(
"Text Example",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 30.0,
color: Color::WHITE,
},
Default::default(),
),
..Default::default()
});
});
});
// right vertical fill
parent
.spawn_bundle(NodeBundle {
style: Style {
flex_direction: FlexDirection::ColumnReverse,
justify_content: JustifyContent::Center,
size: Size::new(Val::Px(200.0), Val::Percent(100.0)),
..Default::default()
},
color: Color::rgb(0.15, 0.15, 0.15).into(),
..Default::default()
})
.with_children(|parent| {
// Title
parent.spawn_bundle(TextBundle {
style: Style {
size: Size::new(Val::Undefined, Val::Px(25.)),
margin: Rect {
left: Val::Auto,
right: Val::Auto,
..Default::default()
},
..Default::default()
},
text: Text::with_section(
"Scrolling list",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 25.,
color: Color::WHITE,
},
Default::default(),
),
..Default::default()
});
// List with hidden overflow
parent
.spawn_bundle(NodeBundle {
style: Style {
flex_direction: FlexDirection::ColumnReverse,
align_self: AlignSelf::Center,
size: Size::new(Val::Percent(100.0), Val::Percent(50.0)),
overflow: Overflow::Hidden,
..Default::default()
},
color: Color::rgb(0.10, 0.10, 0.10).into(),
..Default::default()
})
.with_children(|parent| {
// Moving panel
parent
.spawn_bundle(NodeBundle {
style: Style {
flex_direction: FlexDirection::ColumnReverse,
flex_grow: 1.0,
max_size: Size::new(Val::Undefined, Val::Undefined),
..Default::default()
},
color: Color::NONE.into(),
..Default::default()
})
.insert(ScrollingList::default())
.with_children(|parent| {
// List items
for i in 0..30 {
parent.spawn_bundle(TextBundle {
style: Style {
flex_shrink: 0.,
size: Size::new(Val::Undefined, Val::Px(20.)),
margin: Rect {
left: Val::Auto,
right: Val::Auto,
..Default::default()
},
..Default::default()
},
text: Text::with_section(
format!("Item {}", i),
TextStyle {
font: asset_server
.load("fonts/FiraSans-Bold.ttf"),
font_size: 20.,
color: Color::WHITE,
},
Default::default(),
),
..Default::default()
});
}
});
});
});
// absolute positioning
parent
.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Px(200.0), Val::Px(200.0)),
position_type: PositionType::Absolute,
position: Rect {
left: Val::Px(210.0),
bottom: Val::Px(10.0),
..Default::default()
},
border: Rect::all(Val::Px(20.0)),
..Default::default()
},
color: Color::rgb(0.4, 0.4, 1.0).into(),
..Default::default()
})
.with_children(|parent| {
parent.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
..Default::default()
},
color: Color::rgb(0.8, 0.8, 1.0).into(),
..Default::default()
});
});
// render order test: reddest in the back, whitest in the front (flex center)
parent
.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
position_type: PositionType::Absolute,
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..Default::default()
},
color: Color::NONE.into(),
..Default::default()
})
.with_children(|parent| {
parent
.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Px(100.0), Val::Px(100.0)),
..Default::default()
},
color: Color::rgb(1.0, 0.0, 0.0).into(),
..Default::default()
})
.with_children(|parent| {
parent.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Px(100.0), Val::Px(100.0)),
position_type: PositionType::Absolute,
position: Rect {
left: Val::Px(20.0),
bottom: Val::Px(20.0),
..Default::default()
},
..Default::default()
},
color: Color::rgb(1.0, 0.3, 0.3).into(),
..Default::default()
});
parent.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Px(100.0), Val::Px(100.0)),
position_type: PositionType::Absolute,
position: Rect {
left: Val::Px(40.0),
bottom: Val::Px(40.0),
..Default::default()
},
..Default::default()
},
color: Color::rgb(1.0, 0.5, 0.5).into(),
..Default::default()
});
parent.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Px(100.0), Val::Px(100.0)),
position_type: PositionType::Absolute,
position: Rect {
left: Val::Px(60.0),
bottom: Val::Px(60.0),
..Default::default()
},
..Default::default()
},
color: Color::rgb(1.0, 0.7, 0.7).into(),
..Default::default()
});
// alpha test
parent.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Px(100.0), Val::Px(100.0)),
position_type: PositionType::Absolute,
position: Rect {
left: Val::Px(80.0),
bottom: Val::Px(80.0),
..Default::default()
},
..Default::default()
},
color: Color::rgba(1.0, 0.9, 0.9, 0.4).into(),
..Default::default()
});
});
});
// bevy logo (flex center)
parent
.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
position_type: PositionType::Absolute,
justify_content: JustifyContent::Center,
align_items: AlignItems::FlexEnd,
..Default::default()
},
color: Color::NONE.into(),
..Default::default()
})
.with_children(|parent| {
// bevy logo (image)
parent.spawn_bundle(ImageBundle {
style: Style {
size: Size::new(Val::Px(500.0), Val::Auto),
..Default::default()
},
image: asset_server.load("branding/bevy_logo_dark_big.png").into(),
..Default::default()
});
});
});
}
#[derive(Component, Default)]
struct ScrollingList {
position: f32,
}
fn mouse_scroll(
mut mouse_wheel_events: EventReader<MouseWheel>,
mut query_list: Query<(&mut ScrollingList, &mut Style, &Children, &Node)>,
query_item: Query<&Node>,
) {
for mouse_wheel_event in mouse_wheel_events.iter() {
for (mut scrolling_list, mut style, children, uinode) in query_list.iter_mut() {
let items_height: f32 = children
.iter()
.map(|entity| query_item.get(*entity).unwrap().size.y)
.sum();
let panel_height = uinode.size.y;
let max_scroll = (items_height - panel_height).max(0.);
let dy = match mouse_wheel_event.unit {
MouseScrollUnit::Line => mouse_wheel_event.y * 20.,
MouseScrollUnit::Pixel => mouse_wheel_event.y,
};
scrolling_list.position += dy;
scrolling_list.position = scrolling_list.position.clamp(-max_scroll, 0.);
style.position.top = Val::Px(scrolling_list.position);
}
}
}