mirror of
https://github.com/bevyengine/bevy
synced 2024-11-24 21:53:07 +00:00
ui: focus/click/hover system. initial buttons
This commit is contained in:
parent
19fe299f5a
commit
fe1adb6cf6
18 changed files with 363 additions and 60 deletions
|
@ -176,6 +176,10 @@ path = "examples/shader/shader_custom_material.rs"
|
|||
name = "shader_defs"
|
||||
path = "examples/shader/shader_defs.rs"
|
||||
|
||||
[[example]]
|
||||
name = "button"
|
||||
path = "examples/ui/button.rs"
|
||||
|
||||
[[example]]
|
||||
name = "text"
|
||||
path = "examples/ui/text.rs"
|
||||
|
|
|
@ -14,7 +14,11 @@
|
|||
|
||||
// modified by Bevy contributors
|
||||
|
||||
use core::{marker::PhantomData, ptr::NonNull, ops::{Deref, DerefMut}};
|
||||
use core::{
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
use crate::{archetype::Archetype, Component, Entity};
|
||||
|
||||
|
@ -615,7 +619,7 @@ macro_rules! tuple_impl {
|
|||
let ($($name,)*) = self;
|
||||
($($name.next(),)*)
|
||||
}
|
||||
|
||||
|
||||
unsafe fn should_skip(&self) -> bool {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($name,)*) = self;
|
||||
|
@ -702,58 +706,65 @@ impl<'a, T: Component> Fetch<'a> for FetchMut<T> {
|
|||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub struct Changed<T, Q>(PhantomData<(Q, fn(T))>);
|
||||
pub struct Changed<'a, T> {
|
||||
value: &'a T,
|
||||
}
|
||||
|
||||
impl<T: Component, Q: Query> Query for Changed<T, Q> {
|
||||
type Fetch = FetchChanged<T, Q::Fetch>;
|
||||
impl<'a, T: Component> Deref for Changed<'a, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Component> Query for Changed<'a, T> {
|
||||
type Fetch = FetchChanged<T>;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct FetchChanged<T, F>(F, PhantomData<fn(T)>, NonNull<bool>);
|
||||
pub struct FetchChanged<T>(NonNull<T>, NonNull<bool>);
|
||||
|
||||
impl<'a, T: Component, F: Fetch<'a>> Fetch<'a> for FetchChanged<T, F> {
|
||||
type Item = F::Item;
|
||||
impl<'a, T: Component> Fetch<'a> for FetchChanged<T> {
|
||||
type Item = Changed<'a, T>;
|
||||
|
||||
fn access(archetype: &Archetype) -> Option<Access> {
|
||||
if archetype.has::<T>() {
|
||||
F::access(archetype)
|
||||
Some(Access::Read)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn borrow(archetype: &Archetype) {
|
||||
F::borrow(archetype)
|
||||
archetype.borrow::<T>();
|
||||
}
|
||||
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> {
|
||||
if !archetype.has::<T>() {
|
||||
return None;
|
||||
}
|
||||
Some(Self(
|
||||
F::get(archetype, offset)?,
|
||||
PhantomData,
|
||||
NonNull::new_unchecked(archetype.get_modified::<T>()?.as_ptr().add(offset)),
|
||||
))
|
||||
let components = NonNull::new_unchecked(archetype.get::<T>()?.as_ptr().add(offset));
|
||||
let modified = NonNull::new_unchecked(archetype.get_modified::<T>()?.as_ptr().add(offset));
|
||||
Some(Self(components, modified))
|
||||
}
|
||||
fn release(archetype: &Archetype) {
|
||||
F::release(archetype)
|
||||
archetype.release::<T>();
|
||||
}
|
||||
|
||||
unsafe fn should_skip(&self) -> bool {
|
||||
// skip if the current item wasn't changed
|
||||
!*self.2.as_ref() || self.0.should_skip()
|
||||
!*self.1.as_ref()
|
||||
}
|
||||
|
||||
unsafe fn next(&mut self) -> F::Item {
|
||||
self.2 = NonNull::new_unchecked(self.2.as_ptr().add(1));
|
||||
self.0.next()
|
||||
#[inline]
|
||||
unsafe fn next(&mut self) -> Self::Item {
|
||||
self.1 = NonNull::new_unchecked(self.1.as_ptr().add(1));
|
||||
let value = self.0.as_ptr();
|
||||
self.0 = NonNull::new_unchecked(value.add(1));
|
||||
Changed { value: &*value }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Entity, World, Mut, Changed};
|
||||
use std::{vec::Vec, vec};
|
||||
use crate::{Changed, Entity, Mut, World};
|
||||
use std::{vec, vec::Vec};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -784,8 +795,9 @@ mod tests {
|
|||
|
||||
fn get_changed_a(world: &World) -> Vec<Entity> {
|
||||
world
|
||||
.query::<Changed<A, Entity>>()
|
||||
.query::<(Changed<A>, Entity)>()
|
||||
.iter()
|
||||
.map(|(_a, e)| e)
|
||||
.collect::<Vec<Entity>>()
|
||||
};
|
||||
|
||||
|
@ -823,14 +835,15 @@ mod tests {
|
|||
world.clear_trackers();
|
||||
|
||||
assert!(world
|
||||
.query::<Changed<A, Entity>>()
|
||||
.query::<(Changed<A>, Entity)>()
|
||||
.iter()
|
||||
.map(|(_a, e)| e)
|
||||
.collect::<Vec<Entity>>()
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_changed_query() {
|
||||
fn multiple_changed_query() {
|
||||
let mut world = World::default();
|
||||
world.spawn((A(0), B(0)));
|
||||
let e2 = world.spawn((A(0), B(0)));
|
||||
|
@ -845,16 +858,10 @@ mod tests {
|
|||
}
|
||||
|
||||
let a_b_changed = world
|
||||
.query::<Changed<A, Changed<B, Entity>>>()
|
||||
.query::<(Changed<A>, Changed<B>, Entity)>()
|
||||
.iter()
|
||||
.map(|(_a, _b, e)| e)
|
||||
.collect::<Vec<Entity>>();
|
||||
assert_eq!(a_b_changed, vec![e2]);
|
||||
|
||||
let a_b_changed_tuple = world
|
||||
.query::<(Changed<A, Entity>, Changed<B, &B>)>()
|
||||
.iter()
|
||||
.map(|(e, _b)| e)
|
||||
.collect::<Vec<Entity>>();
|
||||
assert_eq!(a_b_changed_tuple, vec![e2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,6 @@ pub mod prelude {
|
|||
Commands, IntoForEachSystem, IntoQuerySystem, IntoThreadLocalSystem, Query, System,
|
||||
},
|
||||
world::WorldBuilderSource,
|
||||
Bundle, Component, Entity, Ref, RefMut, With, Without, World,
|
||||
Bundle, Changed, Component, Entity, Ref, RefMut, With, Without, World,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -52,6 +52,8 @@ impl ParallelExecutor {
|
|||
executor_stage.run(world, resources, stage_systems);
|
||||
}
|
||||
}
|
||||
|
||||
world.clear_trackers();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -131,6 +131,8 @@ impl Schedule {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
world.clear_trackers();
|
||||
}
|
||||
|
||||
// TODO: move this code to ParallelExecutor
|
||||
|
|
|
@ -150,6 +150,8 @@ impl<'a, Q: HecsQuery> Query<'a, Q> {
|
|||
self.world.query::<Q>()
|
||||
}
|
||||
|
||||
/// Gets a reference to the entity's component of the given type. This will fail if the entity does not have
|
||||
/// the given component type or if the given component type does not match this query.
|
||||
pub fn get<T: Component>(&self, entity: Entity) -> Result<Ref<'_, T>, QueryComponentError> {
|
||||
if let Some(location) = self.world.get_entity_location(entity) {
|
||||
if self
|
||||
|
@ -170,6 +172,8 @@ impl<'a, Q: HecsQuery> Query<'a, Q> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the entity's component of the given type. This will fail if the entity does not have
|
||||
/// the given component type or if the given component type does not match this query.
|
||||
pub fn get_mut<T: Component>(
|
||||
&self,
|
||||
entity: Entity,
|
||||
|
@ -192,6 +196,18 @@ impl<'a, Q: HecsQuery> Query<'a, Q> {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the entity's component to the given value. This will fail if the entity does not already have
|
||||
/// the given component type or if the given component type does not match this query.
|
||||
pub fn set<T: Component>(
|
||||
&self,
|
||||
entity: Entity,
|
||||
component: T,
|
||||
) -> Result<(), QueryComponentError> {
|
||||
let mut current = self.get_mut::<T>(entity)?;
|
||||
*current = component;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct QuerySystemState {
|
||||
|
|
|
@ -23,7 +23,7 @@ unsafe impl Byteable for Color {}
|
|||
|
||||
impl Color {
|
||||
pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0);
|
||||
pub const BLACK: Color = Color::rgb(0.0, 1.0, 0.0);
|
||||
pub const BLACK: Color = Color::rgb(0.0, 0.0, 0.0);
|
||||
pub const RED: Color = Color::rgb(1.0, 0.0, 0.0);
|
||||
pub const GREEN: Color = Color::rgb(0.0, 1.0, 0.0);
|
||||
pub const BLUE: Color = Color::rgb(0.0, 0.0, 1.0);
|
||||
|
|
|
@ -7,9 +7,11 @@ edition = "2018"
|
|||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app" }
|
||||
bevy_asset = { path = "../bevy_asset" }
|
||||
bevy_core = { path = "../bevy_core" }
|
||||
bevy_type_registry = { path = "../bevy_type_registry" }
|
||||
bevy_derive = { path = "../bevy_derive" }
|
||||
bevy_ecs = { path = "../bevy_ecs" }
|
||||
bevy_input = { path = "../bevy_input" }
|
||||
bevy_sprite = { path = "../bevy_sprite" }
|
||||
bevy_text = { path = "../bevy_text" }
|
||||
bevy_transform = { path = "../bevy_transform" }
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use super::Node;
|
||||
use crate::{render::UI_PIPELINE_HANDLE, widget::Label};
|
||||
use crate::{
|
||||
render::UI_PIPELINE_HANDLE,
|
||||
widget::{Button, Label},
|
||||
Click, Hover, FocusPolicy,
|
||||
};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_ecs::Bundle;
|
||||
use bevy_render::{
|
||||
|
@ -11,7 +15,7 @@ use bevy_sprite::{ColorMaterial, QUAD_HANDLE};
|
|||
use bevy_transform::prelude::{Rotation, Scale, Transform, Translation};
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct UiComponents {
|
||||
pub struct NodeComponents {
|
||||
pub node: Node,
|
||||
pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
|
||||
pub material: Handle<ColorMaterial>,
|
||||
|
@ -23,9 +27,9 @@ pub struct UiComponents {
|
|||
pub scale: Scale,
|
||||
}
|
||||
|
||||
impl Default for UiComponents {
|
||||
impl Default for NodeComponents {
|
||||
fn default() -> Self {
|
||||
UiComponents {
|
||||
NodeComponents {
|
||||
mesh: QUAD_HANDLE,
|
||||
render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::specialized(
|
||||
UI_PIPELINE_HANDLE,
|
||||
|
@ -83,3 +87,57 @@ impl Default for LabelComponents {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct ButtonComponents {
|
||||
pub node: Node,
|
||||
pub button: Button,
|
||||
pub click: Click,
|
||||
pub hover: Hover,
|
||||
pub focus_policy: FocusPolicy,
|
||||
pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
|
||||
pub material: Handle<ColorMaterial>,
|
||||
pub draw: Draw,
|
||||
pub render_pipelines: RenderPipelines,
|
||||
pub transform: Transform,
|
||||
pub translation: Translation,
|
||||
pub rotation: Rotation,
|
||||
pub scale: Scale,
|
||||
}
|
||||
|
||||
impl Default for ButtonComponents {
|
||||
fn default() -> Self {
|
||||
ButtonComponents {
|
||||
button: Button,
|
||||
click: Click::default(),
|
||||
hover: Hover::default(),
|
||||
focus_policy: FocusPolicy::default(),
|
||||
mesh: QUAD_HANDLE,
|
||||
render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::specialized(
|
||||
UI_PIPELINE_HANDLE,
|
||||
PipelineSpecialization {
|
||||
dynamic_bindings: vec![
|
||||
// Transform
|
||||
DynamicBinding {
|
||||
bind_group: 1,
|
||||
binding: 0,
|
||||
},
|
||||
// Node_size
|
||||
DynamicBinding {
|
||||
bind_group: 1,
|
||||
binding: 1,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
)]),
|
||||
node: Default::default(),
|
||||
material: Default::default(),
|
||||
draw: Default::default(),
|
||||
transform: Default::default(),
|
||||
translation: Default::default(),
|
||||
rotation: Default::default(),
|
||||
scale: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
133
crates/bevy_ui/src/focus.rs
Normal file
133
crates/bevy_ui/src/focus.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
use crate::Node;
|
||||
use bevy_app::{EventReader, Events};
|
||||
use bevy_core::FloatOrd;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_input::{mouse::MouseButton, Input};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_transform::components::Transform;
|
||||
use bevy_window::{CursorMoved, Windows};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Click {
|
||||
Released,
|
||||
Pressed,
|
||||
}
|
||||
|
||||
impl Default for Click {
|
||||
fn default() -> Self {
|
||||
Click::Released
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Hover {
|
||||
Hovered,
|
||||
NotHovered,
|
||||
}
|
||||
|
||||
impl Default for Hover {
|
||||
fn default() -> Self {
|
||||
Hover::NotHovered
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum FocusPolicy {
|
||||
Block,
|
||||
Pass,
|
||||
}
|
||||
|
||||
impl Default for FocusPolicy {
|
||||
fn default() -> Self {
|
||||
FocusPolicy::Block
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
cursor_moved_event_reader: EventReader<CursorMoved>,
|
||||
cursor_position: Vec2,
|
||||
}
|
||||
|
||||
pub fn ui_focus_system(
|
||||
mut state: Local<State>,
|
||||
windows: Res<Windows>,
|
||||
mouse_button_input: Res<Input<MouseButton>>,
|
||||
cursor_moved_events: Res<Events<CursorMoved>>,
|
||||
mut node_query: Query<(
|
||||
&Node,
|
||||
&Transform,
|
||||
Option<&mut Click>,
|
||||
Option<&mut Hover>,
|
||||
Option<&FocusPolicy>,
|
||||
)>,
|
||||
) {
|
||||
if let Some(cursor_moved) = state.cursor_moved_event_reader.latest(&cursor_moved_events) {
|
||||
state.cursor_position = cursor_moved.position;
|
||||
}
|
||||
|
||||
if mouse_button_input.just_released(MouseButton::Left) {
|
||||
for (_node, _transform, click, _hover, _focus_policy) in &mut node_query.iter() {
|
||||
if let Some(mut click) = click {
|
||||
if *click == Click::Pressed {
|
||||
*click = Click::Released;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mouse_clicked = mouse_button_input.just_pressed(MouseButton::Left);
|
||||
let window = windows.get_primary().unwrap();
|
||||
|
||||
let mut query_iter = node_query.iter();
|
||||
let mut moused_over_z_sorted_nodes = query_iter
|
||||
.iter()
|
||||
.filter_map(|(node, transform, click, hover, focus_policy)| {
|
||||
let position = transform.value.w_axis();
|
||||
// TODO: ui transform is currently in world space, so we need to move it to ui space. we should make these transforms ui space
|
||||
let ui_position = position.truncate().truncate()
|
||||
+ Vec2::new(window.width as f32 / 2.0, window.height as f32 / 2.0);
|
||||
let extents = node.size / 2.0;
|
||||
let min = ui_position - extents;
|
||||
let max = ui_position + extents;
|
||||
// if the current cursor position is within the bounds of the node, consider it for clicking
|
||||
if (min.x()..max.x()).contains(&state.cursor_position.x())
|
||||
&& (min.y()..max.y()).contains(&state.cursor_position.y())
|
||||
{
|
||||
Some((focus_policy, click, hover, FloatOrd(position.z())))
|
||||
} else {
|
||||
if let Some(mut hover) = hover {
|
||||
if *hover == Hover::Hovered {
|
||||
*hover = Hover::NotHovered;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// TODO: sort by negative when we move back to a right handed coordinate system
|
||||
moused_over_z_sorted_nodes.sort_by_key(|(_, _, _, z)| *z);
|
||||
for (focus_policy, click, hover, _) in moused_over_z_sorted_nodes {
|
||||
if mouse_clicked {
|
||||
// only consider nodes with ClickState "clickable"
|
||||
if let Some(mut click) = click {
|
||||
if *click == Click::Released {
|
||||
*click = Click::Pressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
// only consider nodes with Hover "hoverable"
|
||||
if let Some(mut hover) = hover {
|
||||
if *hover == Hover::NotHovered {
|
||||
*hover = Hover::Hovered;
|
||||
}
|
||||
}
|
||||
match focus_policy.cloned().unwrap_or(FocusPolicy::Block) {
|
||||
FocusPolicy::Block => {
|
||||
break;
|
||||
}
|
||||
FocusPolicy::Pass => { /* allow the next node to be hovered/clicked */ }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,30 @@
|
|||
mod anchors;
|
||||
mod focus;
|
||||
pub mod entity;
|
||||
mod margins;
|
||||
mod node;
|
||||
mod render;
|
||||
mod ui_update_system;
|
||||
pub mod update;
|
||||
pub mod widget;
|
||||
|
||||
pub use anchors::*;
|
||||
pub use focus::*;
|
||||
pub use margins::*;
|
||||
pub use node::*;
|
||||
pub use render::*;
|
||||
pub use ui_update_system::*;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{entity::*, widget::Label, Anchors, Margins, Node};
|
||||
pub use crate::{
|
||||
entity::*,
|
||||
widget::{Button, Label},
|
||||
Anchors, Click, Hover, Margins, Node,
|
||||
};
|
||||
}
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::IntoQuerySystem;
|
||||
use bevy_render::render_graph::RenderGraph;
|
||||
use update::ui_update_system;
|
||||
use widget::Label;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -26,7 +32,8 @@ pub struct UiPlugin;
|
|||
|
||||
impl AppPlugin for UiPlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
app.add_system_to_stage(stage::POST_UPDATE, ui_update_system.system())
|
||||
app.add_system_to_stage(stage::PRE_UPDATE, ui_focus_system.system())
|
||||
.add_system_to_stage(stage::POST_UPDATE, ui_update_system.system())
|
||||
.add_system_to_stage(stage::POST_UPDATE, Label::label_system.system())
|
||||
.add_system_to_stage(bevy_render::stage::DRAW, Label::draw_label_system.system());
|
||||
|
||||
|
|
1
crates/bevy_ui/src/widget/button.rs
Normal file
1
crates/bevy_ui/src/widget/button.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub struct Button;
|
|
@ -1,3 +1,5 @@
|
|||
mod button;
|
||||
mod label;
|
||||
|
||||
pub use button::*;
|
||||
pub use label::*;
|
||||
|
|
|
@ -107,9 +107,13 @@ pub fn winit_runner(mut app: App) {
|
|||
app.resources.get_mut::<Events<CursorMoved>>().unwrap();
|
||||
let winit_windows = app.resources.get_mut::<WinitWindows>().unwrap();
|
||||
let window_id = winit_windows.get_window_id(winit_window_id).unwrap();
|
||||
let window = winit_windows.get_window(window_id).unwrap();
|
||||
let inner_size = window.inner_size();
|
||||
// move origin to bottom left
|
||||
let y_position = inner_size.height as f32 - position.y as f32;
|
||||
cursor_moved_events.send(CursorMoved {
|
||||
id: window_id,
|
||||
position: Vec2::new(position.x as f32, position.y as f32),
|
||||
position: Vec2::new(position.x as f32, y_position as f32),
|
||||
});
|
||||
}
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
|
|
65
examples/ui/button.rs
Normal file
65
examples/ui/button.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::build()
|
||||
.add_default_plugins()
|
||||
.add_startup_system(setup.system())
|
||||
.add_system(button_system.system())
|
||||
.run();
|
||||
}
|
||||
|
||||
fn button_system(
|
||||
mut click_query: Query<(&Button, Changed<Click>)>,
|
||||
mut hover_query: Query<(&Button, Changed<Hover>)>,
|
||||
) {
|
||||
for (_button, click) in &mut click_query.iter() {
|
||||
match *click {
|
||||
Click::Pressed => {
|
||||
println!("pressed");
|
||||
}
|
||||
Click::Released => {
|
||||
println!("released");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (_button, hover) in &mut hover_query.iter() {
|
||||
match *hover {
|
||||
Hover::Hovered => {
|
||||
println!("hovered");
|
||||
}
|
||||
Hover::NotHovered => {
|
||||
println!("unhovered");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
) {
|
||||
commands
|
||||
// ui camera
|
||||
.spawn(OrthographicCameraComponents::default())
|
||||
.spawn(ButtonComponents {
|
||||
node: Node::new(Anchors::BOTTOM_LEFT, Margins::new(10.0, 160.0, 10.0, 80.0)),
|
||||
material: materials.add(Color::rgb(0.2, 0.8, 0.2).into()),
|
||||
..Default::default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
parent.spawn(LabelComponents {
|
||||
node: Node::new(Anchors::CENTER, Margins::new(52.0, 10.0, 20.0, 20.0)),
|
||||
label: Label {
|
||||
text: "Button".to_string(),
|
||||
font: asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(),
|
||||
style: TextStyle {
|
||||
font_size: 40.0,
|
||||
color: Color::rgb(0.1, 0.1, 0.1),
|
||||
},
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
}
|
|
@ -25,7 +25,7 @@ fn setup(
|
|||
// ui camera
|
||||
.spawn(OrthographicCameraComponents::default())
|
||||
// root node
|
||||
.spawn(UiComponents {
|
||||
.spawn(NodeComponents {
|
||||
node: Node::new(Anchors::FULL, Margins::default()),
|
||||
material: materials.add(Color::NONE.into()),
|
||||
..Default::default()
|
||||
|
@ -33,7 +33,7 @@ fn setup(
|
|||
.with_children(|parent| {
|
||||
parent
|
||||
// left vertical fill
|
||||
.spawn(UiComponents {
|
||||
.spawn(NodeComponents {
|
||||
node: Node::new(Anchors::LEFT_FULL, Margins::new(10.0, 200.0, 10.0, 10.0)),
|
||||
material: materials.add(Color::rgb(0.02, 0.02, 0.02).into()),
|
||||
..Default::default()
|
||||
|
@ -53,13 +53,13 @@ fn setup(
|
|||
});
|
||||
})
|
||||
// right vertical fill
|
||||
.spawn(UiComponents {
|
||||
.spawn(NodeComponents {
|
||||
node: Node::new(Anchors::RIGHT_FULL, Margins::new(10.0, 100.0, 100.0, 100.0)),
|
||||
material: materials.add(Color::rgb(0.02, 0.02, 0.02).into()),
|
||||
..Default::default()
|
||||
})
|
||||
// render order test: reddest in the back, whitest in the front
|
||||
.spawn(UiComponents {
|
||||
.spawn(NodeComponents {
|
||||
node: Node::positioned(
|
||||
Vec2::new(75.0, 60.0),
|
||||
Anchors::CENTER,
|
||||
|
@ -68,7 +68,7 @@ fn setup(
|
|||
material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()),
|
||||
..Default::default()
|
||||
})
|
||||
.spawn(UiComponents {
|
||||
.spawn(NodeComponents {
|
||||
node: Node::positioned(
|
||||
Vec2::new(50.0, 35.0),
|
||||
Anchors::CENTER,
|
||||
|
@ -77,7 +77,7 @@ fn setup(
|
|||
material: materials.add(Color::rgb(1.0, 0.3, 0.3).into()),
|
||||
..Default::default()
|
||||
})
|
||||
.spawn(UiComponents {
|
||||
.spawn(NodeComponents {
|
||||
node: Node::positioned(
|
||||
Vec2::new(100.0, 85.0),
|
||||
Anchors::CENTER,
|
||||
|
@ -86,7 +86,7 @@ fn setup(
|
|||
material: materials.add(Color::rgb(1.0, 0.5, 0.5).into()),
|
||||
..Default::default()
|
||||
})
|
||||
.spawn(UiComponents {
|
||||
.spawn(NodeComponents {
|
||||
node: Node::positioned(
|
||||
Vec2::new(150.0, 135.0),
|
||||
Anchors::CENTER,
|
||||
|
@ -96,7 +96,7 @@ fn setup(
|
|||
..Default::default()
|
||||
})
|
||||
// parenting
|
||||
.spawn(UiComponents {
|
||||
.spawn(NodeComponents {
|
||||
node: Node::positioned(
|
||||
Vec2::new(210.0, 0.0),
|
||||
Anchors::BOTTOM_LEFT,
|
||||
|
@ -106,14 +106,14 @@ fn setup(
|
|||
..Default::default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
parent.spawn(UiComponents {
|
||||
parent.spawn(NodeComponents {
|
||||
node: Node::new(Anchors::FULL, Margins::new(20.0, 20.0, 20.0, 20.0)),
|
||||
material: materials.add(Color::rgb(0.6, 0.6, 1.0).into()),
|
||||
..Default::default()
|
||||
});
|
||||
})
|
||||
// alpha test
|
||||
.spawn(UiComponents {
|
||||
.spawn(NodeComponents {
|
||||
node: Node::positioned(
|
||||
Vec2::new(200.0, 185.0),
|
||||
Anchors::CENTER,
|
||||
|
@ -123,7 +123,7 @@ fn setup(
|
|||
..Default::default()
|
||||
})
|
||||
// texture
|
||||
.spawn(UiComponents {
|
||||
.spawn(NodeComponents {
|
||||
node: Node::new(
|
||||
Anchors::CENTER_TOP,
|
||||
Margins::new(-250.0, 250.0, 510.0 * aspect, 10.0),
|
||||
|
|
|
@ -29,7 +29,7 @@ fn setup(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
|
|||
for i in 0..count {
|
||||
// 2d camera
|
||||
let cur = Vec2::new(1.0, 1.0) + prev;
|
||||
commands.spawn(UiComponents {
|
||||
commands.spawn(NodeComponents {
|
||||
node: Node {
|
||||
position: Vec2::new(75.0, 75.0) + cur,
|
||||
anchors: Anchors::new(0.5, 0.5, 0.5, 0.5),
|
||||
|
|
Loading…
Reference in a new issue