ui: focus/click/hover system. initial buttons
This commit is contained in:
parent
19fe299f5a
commit
fe1adb6cf6
@ -176,6 +176,10 @@ path = "examples/shader/shader_custom_material.rs"
|
|||||||
name = "shader_defs"
|
name = "shader_defs"
|
||||||
path = "examples/shader/shader_defs.rs"
|
path = "examples/shader/shader_defs.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "button"
|
||||||
|
path = "examples/ui/button.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "text"
|
name = "text"
|
||||||
path = "examples/ui/text.rs"
|
path = "examples/ui/text.rs"
|
||||||
|
|||||||
@ -14,7 +14,11 @@
|
|||||||
|
|
||||||
// modified by Bevy contributors
|
// 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};
|
use crate::{archetype::Archetype, Component, Entity};
|
||||||
|
|
||||||
@ -615,7 +619,7 @@ macro_rules! tuple_impl {
|
|||||||
let ($($name,)*) = self;
|
let ($($name,)*) = self;
|
||||||
($($name.next(),)*)
|
($($name.next(),)*)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn should_skip(&self) -> bool {
|
unsafe fn should_skip(&self) -> bool {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let ($($name,)*) = self;
|
let ($($name,)*) = self;
|
||||||
@ -702,58 +706,65 @@ impl<'a, T: Component> Fetch<'a> for FetchMut<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[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> {
|
impl<'a, T: Component> Deref for Changed<'a, T> {
|
||||||
type Fetch = FetchChanged<T, Q::Fetch>;
|
type Target = T;
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Component> Query for Changed<'a, T> {
|
||||||
|
type Fetch = FetchChanged<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[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> {
|
impl<'a, T: Component> Fetch<'a> for FetchChanged<T> {
|
||||||
type Item = F::Item;
|
type Item = Changed<'a, T>;
|
||||||
|
|
||||||
fn access(archetype: &Archetype) -> Option<Access> {
|
fn access(archetype: &Archetype) -> Option<Access> {
|
||||||
if archetype.has::<T>() {
|
if archetype.has::<T>() {
|
||||||
F::access(archetype)
|
Some(Access::Read)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn borrow(archetype: &Archetype) {
|
fn borrow(archetype: &Archetype) {
|
||||||
F::borrow(archetype)
|
archetype.borrow::<T>();
|
||||||
}
|
}
|
||||||
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> {
|
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> {
|
||||||
if !archetype.has::<T>() {
|
let components = NonNull::new_unchecked(archetype.get::<T>()?.as_ptr().add(offset));
|
||||||
return None;
|
let modified = NonNull::new_unchecked(archetype.get_modified::<T>()?.as_ptr().add(offset));
|
||||||
}
|
Some(Self(components, modified))
|
||||||
Some(Self(
|
|
||||||
F::get(archetype, offset)?,
|
|
||||||
PhantomData,
|
|
||||||
NonNull::new_unchecked(archetype.get_modified::<T>()?.as_ptr().add(offset)),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
fn release(archetype: &Archetype) {
|
fn release(archetype: &Archetype) {
|
||||||
F::release(archetype)
|
archetype.release::<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn should_skip(&self) -> bool {
|
unsafe fn should_skip(&self) -> bool {
|
||||||
// skip if the current item wasn't changed
|
// 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 {
|
#[inline]
|
||||||
self.2 = NonNull::new_unchecked(self.2.as_ptr().add(1));
|
unsafe fn next(&mut self) -> Self::Item {
|
||||||
self.0.next()
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Entity, World, Mut, Changed};
|
use crate::{Changed, Entity, Mut, World};
|
||||||
use std::{vec::Vec, vec};
|
use std::{vec, vec::Vec};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -784,8 +795,9 @@ mod tests {
|
|||||||
|
|
||||||
fn get_changed_a(world: &World) -> Vec<Entity> {
|
fn get_changed_a(world: &World) -> Vec<Entity> {
|
||||||
world
|
world
|
||||||
.query::<Changed<A, Entity>>()
|
.query::<(Changed<A>, Entity)>()
|
||||||
.iter()
|
.iter()
|
||||||
|
.map(|(_a, e)| e)
|
||||||
.collect::<Vec<Entity>>()
|
.collect::<Vec<Entity>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -823,14 +835,15 @@ mod tests {
|
|||||||
world.clear_trackers();
|
world.clear_trackers();
|
||||||
|
|
||||||
assert!(world
|
assert!(world
|
||||||
.query::<Changed<A, Entity>>()
|
.query::<(Changed<A>, Entity)>()
|
||||||
.iter()
|
.iter()
|
||||||
|
.map(|(_a, e)| e)
|
||||||
.collect::<Vec<Entity>>()
|
.collect::<Vec<Entity>>()
|
||||||
.is_empty());
|
.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_changed_query() {
|
fn multiple_changed_query() {
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
world.spawn((A(0), B(0)));
|
world.spawn((A(0), B(0)));
|
||||||
let e2 = 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
|
let a_b_changed = world
|
||||||
.query::<Changed<A, Changed<B, Entity>>>()
|
.query::<(Changed<A>, Changed<B>, Entity)>()
|
||||||
.iter()
|
.iter()
|
||||||
|
.map(|(_a, _b, e)| e)
|
||||||
.collect::<Vec<Entity>>();
|
.collect::<Vec<Entity>>();
|
||||||
assert_eq!(a_b_changed, vec![e2]);
|
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,
|
Commands, IntoForEachSystem, IntoQuerySystem, IntoThreadLocalSystem, Query, System,
|
||||||
},
|
},
|
||||||
world::WorldBuilderSource,
|
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);
|
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
|
// TODO: move this code to ParallelExecutor
|
||||||
|
|||||||
@ -150,6 +150,8 @@ impl<'a, Q: HecsQuery> Query<'a, Q> {
|
|||||||
self.world.query::<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> {
|
pub fn get<T: Component>(&self, entity: Entity) -> Result<Ref<'_, T>, QueryComponentError> {
|
||||||
if let Some(location) = self.world.get_entity_location(entity) {
|
if let Some(location) = self.world.get_entity_location(entity) {
|
||||||
if self
|
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>(
|
pub fn get_mut<T: Component>(
|
||||||
&self,
|
&self,
|
||||||
entity: Entity,
|
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 {
|
struct QuerySystemState {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ unsafe impl Byteable for Color {}
|
|||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0);
|
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 RED: Color = Color::rgb(1.0, 0.0, 0.0);
|
||||||
pub const GREEN: Color = Color::rgb(0.0, 1.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);
|
pub const BLUE: Color = Color::rgb(0.0, 0.0, 1.0);
|
||||||
|
|||||||
@ -7,9 +7,11 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_app = { path = "../bevy_app" }
|
bevy_app = { path = "../bevy_app" }
|
||||||
bevy_asset = { path = "../bevy_asset" }
|
bevy_asset = { path = "../bevy_asset" }
|
||||||
|
bevy_core = { path = "../bevy_core" }
|
||||||
bevy_type_registry = { path = "../bevy_type_registry" }
|
bevy_type_registry = { path = "../bevy_type_registry" }
|
||||||
bevy_derive = { path = "../bevy_derive" }
|
bevy_derive = { path = "../bevy_derive" }
|
||||||
bevy_ecs = { path = "../bevy_ecs" }
|
bevy_ecs = { path = "../bevy_ecs" }
|
||||||
|
bevy_input = { path = "../bevy_input" }
|
||||||
bevy_sprite = { path = "../bevy_sprite" }
|
bevy_sprite = { path = "../bevy_sprite" }
|
||||||
bevy_text = { path = "../bevy_text" }
|
bevy_text = { path = "../bevy_text" }
|
||||||
bevy_transform = { path = "../bevy_transform" }
|
bevy_transform = { path = "../bevy_transform" }
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
use super::Node;
|
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_asset::Handle;
|
||||||
use bevy_ecs::Bundle;
|
use bevy_ecs::Bundle;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
@ -11,7 +15,7 @@ use bevy_sprite::{ColorMaterial, QUAD_HANDLE};
|
|||||||
use bevy_transform::prelude::{Rotation, Scale, Transform, Translation};
|
use bevy_transform::prelude::{Rotation, Scale, Transform, Translation};
|
||||||
|
|
||||||
#[derive(Bundle)]
|
#[derive(Bundle)]
|
||||||
pub struct UiComponents {
|
pub struct NodeComponents {
|
||||||
pub node: Node,
|
pub node: Node,
|
||||||
pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
|
pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
|
||||||
pub material: Handle<ColorMaterial>,
|
pub material: Handle<ColorMaterial>,
|
||||||
@ -23,9 +27,9 @@ pub struct UiComponents {
|
|||||||
pub scale: Scale,
|
pub scale: Scale,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UiComponents {
|
impl Default for NodeComponents {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
UiComponents {
|
NodeComponents {
|
||||||
mesh: QUAD_HANDLE,
|
mesh: QUAD_HANDLE,
|
||||||
render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::specialized(
|
render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::specialized(
|
||||||
UI_PIPELINE_HANDLE,
|
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 anchors;
|
||||||
|
mod focus;
|
||||||
pub mod entity;
|
pub mod entity;
|
||||||
mod margins;
|
mod margins;
|
||||||
mod node;
|
mod node;
|
||||||
mod render;
|
mod render;
|
||||||
mod ui_update_system;
|
pub mod update;
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
|
|
||||||
pub use anchors::*;
|
pub use anchors::*;
|
||||||
|
pub use focus::*;
|
||||||
pub use margins::*;
|
pub use margins::*;
|
||||||
pub use node::*;
|
pub use node::*;
|
||||||
pub use render::*;
|
pub use render::*;
|
||||||
pub use ui_update_system::*;
|
|
||||||
|
|
||||||
pub mod prelude {
|
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_app::prelude::*;
|
||||||
use bevy_ecs::IntoQuerySystem;
|
use bevy_ecs::IntoQuerySystem;
|
||||||
use bevy_render::render_graph::RenderGraph;
|
use bevy_render::render_graph::RenderGraph;
|
||||||
|
use update::ui_update_system;
|
||||||
use widget::Label;
|
use widget::Label;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -26,7 +32,8 @@ pub struct UiPlugin;
|
|||||||
|
|
||||||
impl AppPlugin for UiPlugin {
|
impl AppPlugin for UiPlugin {
|
||||||
fn build(&self, app: &mut AppBuilder) {
|
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(stage::POST_UPDATE, Label::label_system.system())
|
||||||
.add_system_to_stage(bevy_render::stage::DRAW, Label::draw_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;
|
mod label;
|
||||||
|
|
||||||
|
pub use button::*;
|
||||||
pub use label::*;
|
pub use label::*;
|
||||||
|
|||||||
@ -107,9 +107,13 @@ pub fn winit_runner(mut app: App) {
|
|||||||
app.resources.get_mut::<Events<CursorMoved>>().unwrap();
|
app.resources.get_mut::<Events<CursorMoved>>().unwrap();
|
||||||
let winit_windows = app.resources.get_mut::<WinitWindows>().unwrap();
|
let winit_windows = app.resources.get_mut::<WinitWindows>().unwrap();
|
||||||
let window_id = winit_windows.get_window_id(winit_window_id).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 {
|
cursor_moved_events.send(CursorMoved {
|
||||||
id: window_id,
|
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, .. } => {
|
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
|
// ui camera
|
||||||
.spawn(OrthographicCameraComponents::default())
|
.spawn(OrthographicCameraComponents::default())
|
||||||
// root node
|
// root node
|
||||||
.spawn(UiComponents {
|
.spawn(NodeComponents {
|
||||||
node: Node::new(Anchors::FULL, Margins::default()),
|
node: Node::new(Anchors::FULL, Margins::default()),
|
||||||
material: materials.add(Color::NONE.into()),
|
material: materials.add(Color::NONE.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -33,7 +33,7 @@ fn setup(
|
|||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent
|
parent
|
||||||
// left vertical fill
|
// left vertical fill
|
||||||
.spawn(UiComponents {
|
.spawn(NodeComponents {
|
||||||
node: Node::new(Anchors::LEFT_FULL, Margins::new(10.0, 200.0, 10.0, 10.0)),
|
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()),
|
material: materials.add(Color::rgb(0.02, 0.02, 0.02).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -53,13 +53,13 @@ fn setup(
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
// right vertical fill
|
// right vertical fill
|
||||||
.spawn(UiComponents {
|
.spawn(NodeComponents {
|
||||||
node: Node::new(Anchors::RIGHT_FULL, Margins::new(10.0, 100.0, 100.0, 100.0)),
|
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()),
|
material: materials.add(Color::rgb(0.02, 0.02, 0.02).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
// render order test: reddest in the back, whitest in the front
|
// render order test: reddest in the back, whitest in the front
|
||||||
.spawn(UiComponents {
|
.spawn(NodeComponents {
|
||||||
node: Node::positioned(
|
node: Node::positioned(
|
||||||
Vec2::new(75.0, 60.0),
|
Vec2::new(75.0, 60.0),
|
||||||
Anchors::CENTER,
|
Anchors::CENTER,
|
||||||
@ -68,7 +68,7 @@ fn setup(
|
|||||||
material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()),
|
material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.spawn(UiComponents {
|
.spawn(NodeComponents {
|
||||||
node: Node::positioned(
|
node: Node::positioned(
|
||||||
Vec2::new(50.0, 35.0),
|
Vec2::new(50.0, 35.0),
|
||||||
Anchors::CENTER,
|
Anchors::CENTER,
|
||||||
@ -77,7 +77,7 @@ fn setup(
|
|||||||
material: materials.add(Color::rgb(1.0, 0.3, 0.3).into()),
|
material: materials.add(Color::rgb(1.0, 0.3, 0.3).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.spawn(UiComponents {
|
.spawn(NodeComponents {
|
||||||
node: Node::positioned(
|
node: Node::positioned(
|
||||||
Vec2::new(100.0, 85.0),
|
Vec2::new(100.0, 85.0),
|
||||||
Anchors::CENTER,
|
Anchors::CENTER,
|
||||||
@ -86,7 +86,7 @@ fn setup(
|
|||||||
material: materials.add(Color::rgb(1.0, 0.5, 0.5).into()),
|
material: materials.add(Color::rgb(1.0, 0.5, 0.5).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.spawn(UiComponents {
|
.spawn(NodeComponents {
|
||||||
node: Node::positioned(
|
node: Node::positioned(
|
||||||
Vec2::new(150.0, 135.0),
|
Vec2::new(150.0, 135.0),
|
||||||
Anchors::CENTER,
|
Anchors::CENTER,
|
||||||
@ -96,7 +96,7 @@ fn setup(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
// parenting
|
// parenting
|
||||||
.spawn(UiComponents {
|
.spawn(NodeComponents {
|
||||||
node: Node::positioned(
|
node: Node::positioned(
|
||||||
Vec2::new(210.0, 0.0),
|
Vec2::new(210.0, 0.0),
|
||||||
Anchors::BOTTOM_LEFT,
|
Anchors::BOTTOM_LEFT,
|
||||||
@ -106,14 +106,14 @@ fn setup(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(UiComponents {
|
parent.spawn(NodeComponents {
|
||||||
node: Node::new(Anchors::FULL, Margins::new(20.0, 20.0, 20.0, 20.0)),
|
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()),
|
material: materials.add(Color::rgb(0.6, 0.6, 1.0).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
// alpha test
|
// alpha test
|
||||||
.spawn(UiComponents {
|
.spawn(NodeComponents {
|
||||||
node: Node::positioned(
|
node: Node::positioned(
|
||||||
Vec2::new(200.0, 185.0),
|
Vec2::new(200.0, 185.0),
|
||||||
Anchors::CENTER,
|
Anchors::CENTER,
|
||||||
@ -123,7 +123,7 @@ fn setup(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
// texture
|
// texture
|
||||||
.spawn(UiComponents {
|
.spawn(NodeComponents {
|
||||||
node: Node::new(
|
node: Node::new(
|
||||||
Anchors::CENTER_TOP,
|
Anchors::CENTER_TOP,
|
||||||
Margins::new(-250.0, 250.0, 510.0 * aspect, 10.0),
|
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 {
|
for i in 0..count {
|
||||||
// 2d camera
|
// 2d camera
|
||||||
let cur = Vec2::new(1.0, 1.0) + prev;
|
let cur = Vec2::new(1.0, 1.0) + prev;
|
||||||
commands.spawn(UiComponents {
|
commands.spawn(NodeComponents {
|
||||||
node: Node {
|
node: Node {
|
||||||
position: Vec2::new(75.0, 75.0) + cur,
|
position: Vec2::new(75.0, 75.0) + cur,
|
||||||
anchors: Anchors::new(0.5, 0.5, 0.5, 0.5),
|
anchors: Anchors::new(0.5, 0.5, 0.5, 0.5),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user