More poking at Floating
.
This commit is contained in:
parent
588989a4d0
commit
8ed8666cbf
@ -17,7 +17,8 @@ use crate::portal::{PortalTraversal, PortalTraversalItem};
|
||||
///
|
||||
/// Focus navigation: the menu may be part of a composite of multiple menus such as a menu bar.
|
||||
/// This means that depending on direction, focus movement may move to the next menu item, or
|
||||
/// the next menu.
|
||||
/// the next menu. This also means that different events will often be handled at different
|
||||
/// levels of the hierarchy - some being handled by the popup, and some by the popup's owner.
|
||||
#[derive(Event, EntityEvent, Clone)]
|
||||
pub enum MenuEvent {
|
||||
/// Indicates we want to open the menu, if it is not already open.
|
||||
@ -28,6 +29,10 @@ pub enum MenuEvent {
|
||||
/// Move the input focs to the parent element. This usually happens as the menu is closing,
|
||||
/// although will not happen if the close was a result of clicking on the background.
|
||||
FocusParent,
|
||||
/// Move the input focus to the first child in the parent's hierarchy (Home).
|
||||
FocusFirst,
|
||||
/// Move the input focus to the last child in the parent's hierarchy (End).
|
||||
FocusLast,
|
||||
/// Move the input focus to the previous child in the parent's hierarchy (Shift-Tab).
|
||||
FocusPrev,
|
||||
/// Move the input focus to the next child in the parent's hierarchy (Tab).
|
||||
@ -63,16 +68,16 @@ impl Traversal<MenuEvent> for PortalTraversal {
|
||||
}
|
||||
}
|
||||
|
||||
/// Component that defines a popup menu
|
||||
/// Component that defines a popup menu container.
|
||||
#[derive(Component, Debug)]
|
||||
#[require(AccessibilityNode(accesskit::Node::new(Role::MenuListPopup)))]
|
||||
pub struct CoreMenuPopup;
|
||||
|
||||
/// Component that defines a menu item
|
||||
/// Component that defines a menu item.
|
||||
#[derive(Component, Debug)]
|
||||
#[require(AccessibilityNode(accesskit::Node::new(Role::MenuItem)))]
|
||||
pub struct CoreMenuItem {
|
||||
/// Optional system to run when the menu item is clicked, or when the Enter or Space key
|
||||
/// is pressed while the button is focused.
|
||||
/// is pressed while the item is focused.
|
||||
pub on_click: Option<SystemId>,
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
//! Framework for positioning of popups, tooltips, and other floating UI elements.
|
||||
|
||||
use bevy_app::{App, Plugin, PostUpdate};
|
||||
use bevy_ecs::{component::Component, entity::Entity, query::Without, system::Query};
|
||||
use bevy_app::{App, Plugin, PreUpdate};
|
||||
use bevy_ecs::{
|
||||
component::Component, entity::Entity, query::Without, schedule::IntoScheduleConfigs,
|
||||
system::Query,
|
||||
};
|
||||
use bevy_math::{Rect, Vec2};
|
||||
use bevy_ui::{ComputedNode, ComputedNodeTarget, Node, UiGlobalTransform, Val};
|
||||
use bevy_ui::{
|
||||
ComputedNode, ComputedNodeTarget, Node, PositionType, UiGlobalTransform, UiSystems, Val,
|
||||
};
|
||||
|
||||
/// Which side of the anchor element the floating element should be placed.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
@ -102,6 +107,11 @@ fn position_floating(
|
||||
q_anchor: Query<(&ComputedNode, &UiGlobalTransform), Without<Floating>>,
|
||||
) {
|
||||
for (mut node, computed_node, computed_target, floating) in q_float.iter_mut() {
|
||||
// Logical size isn't set initially, ignore until it is.
|
||||
if computed_target.logical_size().length_squared() == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// A rectangle which represents the area of the window.
|
||||
let window_rect = Rect {
|
||||
min: Vec2::ZERO,
|
||||
@ -114,7 +124,10 @@ fn position_floating(
|
||||
let Ok((anchor_node, anchor_transform)) = q_anchor.get(anchor_entity) else {
|
||||
continue;
|
||||
};
|
||||
Rect::from_center_size(anchor_transform.translation, anchor_node.size())
|
||||
Rect::from_center_size(
|
||||
anchor_transform.translation * anchor_node.inverse_scale_factor,
|
||||
anchor_node.size() * anchor_node.inverse_scale_factor,
|
||||
)
|
||||
}
|
||||
FloatAnchor::Rect(rect) => rect,
|
||||
};
|
||||
@ -125,7 +138,7 @@ fn position_floating(
|
||||
|
||||
// Loop through all the potential positions and find a good one.
|
||||
for position in &floating.positions {
|
||||
let float_size = computed_node.size();
|
||||
let float_size = computed_node.size() * computed_node.inverse_scale_factor;
|
||||
let mut rect = Rect::default();
|
||||
|
||||
// Taraget width and height depends on whether 'stretch' is true.
|
||||
@ -225,6 +238,7 @@ fn position_floating(
|
||||
if best_occluded < f32::MAX {
|
||||
node.left = Val::Px(best_rect.min.x);
|
||||
node.top = Val::Px(best_rect.min.y);
|
||||
node.position_type = PositionType::Absolute;
|
||||
if best_position.stretch {
|
||||
match best_position.side {
|
||||
FloatSide::Top | FloatSide::Bottom => {
|
||||
@ -245,6 +259,6 @@ pub struct FloatingPlugin;
|
||||
|
||||
impl Plugin for FloatingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(PostUpdate, position_floating);
|
||||
app.add_systems(PreUpdate, position_floating.in_set(UiSystems::Prepare));
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,19 @@
|
||||
//! Relationships for defining "portal children", where the term "portal" refers to a mechanism
|
||||
//! whereby a logical child node can be physically located at a different point in the hierarchy.
|
||||
//! The "portal" represents a logical connection between the child and it's parent which is not
|
||||
//! a normal child relationship.
|
||||
//! Relationships for defining "portal children".
|
||||
//!
|
||||
//! The term "portal" is commonly used in web user interface libraries to mean a mechanism whereby a
|
||||
//! parent element can have a logical child which is physically present elsewhere in the hierarchy.
|
||||
//! In this case, it means that for rendering and layout purposes, the child acts as a root node,
|
||||
//! but for purposes of event bubbling and ownership, it acts as a child.
|
||||
//!
|
||||
//! This is typically used for UI elements such as menus and dialogs which need to calculate their
|
||||
//! positions in window coordinates, despite being owned by UI elements nested deep within the
|
||||
//! hierarchy.
|
||||
|
||||
use bevy_ecs::{component::Component, entity::Entity, hierarchy::ChildOf, query::QueryData};
|
||||
|
||||
/// Defines the portal child relationship. For purposes of despawning, a portal child behaves
|
||||
/// as if it's a real child. However, for purpose of rendering and layout, a portal child behaves
|
||||
/// as if it's a root element. Certain events can also bubble via the portal relationship.
|
||||
/// as if it's a root element. Certain events can also bubble through the portal relationship.
|
||||
#[derive(Component, Clone, PartialEq, Eq, Debug)]
|
||||
#[relationship(relationship_target = PortalChildren)]
|
||||
pub struct PortalChildOf(#[entities] pub Entity);
|
||||
@ -25,7 +31,7 @@ impl PortalChildOf {
|
||||
#[relationship_target(relationship = PortalChildOf, linked_spawn)]
|
||||
pub struct PortalChildren(Vec<Entity>);
|
||||
|
||||
/// A traversal that uses either the [`ChildOf`] or [`PortalChildOf`] relationship. If the
|
||||
/// A traversal algorithm that uses either the [`ChildOf`] or [`PortalChildOf`] relationship. If the
|
||||
/// entity has both relations, the latter takes precedence.
|
||||
#[derive(QueryData)]
|
||||
pub struct PortalTraversal {
|
||||
|
@ -78,6 +78,10 @@ struct DemoCheckbox;
|
||||
#[derive(Component, Default)]
|
||||
struct DemoRadio(TrackClick);
|
||||
|
||||
/// Menuy button styling marker
|
||||
#[derive(Component)]
|
||||
struct DemoMenuButton;
|
||||
|
||||
/// A struct to hold the state of various widgets shown in the demo.
|
||||
///
|
||||
/// While it is possible to use the widget's own state components as the source of truth,
|
||||
@ -132,6 +136,8 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
|
||||
},
|
||||
);
|
||||
|
||||
let on_open_menu = commands.register_system(spawn_popup);
|
||||
|
||||
// System to update a resource when the radio group changes.
|
||||
let on_change_radio = commands.register_system(
|
||||
|value: In<Entity>,
|
||||
@ -213,6 +219,49 @@ fn button(asset_server: &AssetServer, on_click: Callback) -> impl Bundle {
|
||||
)
|
||||
}
|
||||
|
||||
fn menu_button(asset_server: &AssetServer, on_click: SystemId) -> impl Bundle {
|
||||
(
|
||||
Node {
|
||||
width: Val::Px(200.0),
|
||||
height: Val::Px(65.0),
|
||||
border: UiRect::all(Val::Px(5.0)),
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::axes(Val::Px(16.0), Val::Px(0.0)),
|
||||
..default()
|
||||
},
|
||||
DemoMenuButton,
|
||||
CoreButton {
|
||||
on_click: Callback::System(on_click),
|
||||
},
|
||||
Hovered::default(),
|
||||
TabIndex(0),
|
||||
BorderColor::all(Color::BLACK),
|
||||
BorderRadius::all(Val::Px(5.0)),
|
||||
BackgroundColor(NORMAL_BUTTON),
|
||||
children![
|
||||
(
|
||||
Text::new("Menu"),
|
||||
TextFont {
|
||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||
font_size: 33.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(Color::srgb(0.9, 0.9, 0.9)),
|
||||
TextShadow::default(),
|
||||
),
|
||||
(
|
||||
Node {
|
||||
width: Val::Px(12.0),
|
||||
height: Val::Px(12.0),
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(GRAY.into()),
|
||||
)
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn update_button_style(
|
||||
mut buttons: Query<
|
||||
(
|
||||
@ -739,6 +788,43 @@ fn radio(asset_server: &AssetServer, value: TrackClick, caption: &str) -> impl B
|
||||
)
|
||||
}
|
||||
|
||||
fn spawn_popup(menu: Query<Entity, With<DemoMenuButton>>, mut commands: Commands) {
|
||||
let Ok(anchor) = menu.single() else {
|
||||
return;
|
||||
};
|
||||
commands.entity(anchor).insert(PortalChildren::spawn_one((
|
||||
Node {
|
||||
min_height: Val::Px(100.),
|
||||
min_width: Val::Px(100.),
|
||||
border: UiRect::all(Val::Px(2.0)),
|
||||
position_type: PositionType::Absolute,
|
||||
left: Val::Px(100.),
|
||||
..default()
|
||||
},
|
||||
BorderColor::all(GREEN.into()),
|
||||
BackgroundColor(GRAY.into()),
|
||||
ZIndex(100),
|
||||
Floating {
|
||||
anchor: FloatAnchor::Node(anchor),
|
||||
positions: vec![
|
||||
FloatPosition {
|
||||
side: FloatSide::Bottom,
|
||||
align: FloatAlign::Start,
|
||||
gap: 2.0,
|
||||
..default()
|
||||
},
|
||||
FloatPosition {
|
||||
side: FloatSide::Top,
|
||||
align: FloatAlign::Start,
|
||||
gap: 2.0,
|
||||
..default()
|
||||
},
|
||||
],
|
||||
},
|
||||
)));
|
||||
info!("Open menu");
|
||||
}
|
||||
|
||||
fn toggle_disabled(
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
mut interaction_query: Query<
|
||||
|
Loading…
Reference in New Issue
Block a user