mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
Merge branch 'FyroxEngine:master' into inspector-fix
This commit is contained in:
+310
-57
@@ -7,13 +7,14 @@ use crate::{
|
||||
border::BorderBuilder,
|
||||
brush::Brush,
|
||||
core::{
|
||||
algebra::Vector2, color::Color, parking_lot::Mutex, pool::Handle, reflect::prelude::*,
|
||||
type_traits::prelude::*, uuid_provider, visitor::prelude::*,
|
||||
algebra::Vector2, color::Color, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
|
||||
uuid_provider, variable::InheritableVariable, visitor::prelude::*,
|
||||
},
|
||||
decorator::{DecoratorBuilder, DecoratorMessage},
|
||||
define_constructor,
|
||||
draw::DrawingContext,
|
||||
grid::{Column, GridBuilder, Row},
|
||||
message::{ButtonState, MessageDirection, OsEvent, UiMessage},
|
||||
message::{ButtonState, KeyCode, MessageDirection, OsEvent, UiMessage},
|
||||
popup::{Placement, Popup, PopupBuilder, PopupMessage},
|
||||
stack_panel::StackPanelBuilder,
|
||||
text::TextBuilder,
|
||||
@@ -23,12 +24,11 @@ use crate::{
|
||||
BuildContext, Control, HorizontalAlignment, Orientation, RestrictionEntry, Thickness, UiNode,
|
||||
UserInterface, VerticalAlignment, BRUSH_BRIGHT, BRUSH_BRIGHT_BLUE, BRUSH_PRIMARY,
|
||||
};
|
||||
use fyrox_core::variable::InheritableVariable;
|
||||
use fyrox_graph::{BaseSceneGraph, SceneGraph};
|
||||
use fyrox_graph::{BaseSceneGraph, SceneGraph, SceneGraphNode};
|
||||
use std::any::TypeId;
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
sync::mpsc::Sender,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// A set of messages that can be used to manipulate a [`Menu`] widget at runtime.
|
||||
@@ -58,7 +58,10 @@ pub enum MenuItemMessage {
|
||||
/// Opens the menu item's popup with inner items.
|
||||
Open,
|
||||
/// Closes the menu item's popup with inner items.
|
||||
Close,
|
||||
Close {
|
||||
/// Defines, whether the item should be deselected when closed or not.
|
||||
deselect: bool,
|
||||
},
|
||||
/// The message is generated by a menu item when it is clicked.
|
||||
Click,
|
||||
/// Adds a new item to the menu item.
|
||||
@@ -67,6 +70,8 @@ pub enum MenuItemMessage {
|
||||
RemoveItem(Handle<UiNode>),
|
||||
/// Sets the new items of the menu item.
|
||||
Items(Vec<Handle<UiNode>>),
|
||||
/// Selects/deselects the item.
|
||||
Select(bool),
|
||||
}
|
||||
|
||||
impl MenuItemMessage {
|
||||
@@ -76,7 +81,7 @@ impl MenuItemMessage {
|
||||
);
|
||||
define_constructor!(
|
||||
/// Creates [`MenuItemMessage::Close`] message.
|
||||
MenuItemMessage:Close => fn close(), layout: false
|
||||
MenuItemMessage:Close => fn close(deselect: bool), layout: false
|
||||
);
|
||||
define_constructor!(
|
||||
/// Creates [`MenuItemMessage::Click`] message.
|
||||
@@ -94,6 +99,10 @@ impl MenuItemMessage {
|
||||
/// Creates [`MenuItemMessage::Items`] message.
|
||||
MenuItemMessage:Items => fn items(Vec<Handle<UiNode>>), layout: false
|
||||
);
|
||||
define_constructor!(
|
||||
/// Creates [`MenuItemMessage::Select`] message.
|
||||
MenuItemMessage:Select => fn select(bool), layout: false
|
||||
);
|
||||
}
|
||||
|
||||
/// Menu widget is a root widget of an arbitrary menu hierarchy. An example could be "standard" menu strip with `File`, `Edit`, `View`, etc.
|
||||
@@ -151,6 +160,8 @@ impl MenuItemMessage {
|
||||
pub struct Menu {
|
||||
widget: Widget,
|
||||
active: bool,
|
||||
#[component(include)]
|
||||
items: ItemsContainer,
|
||||
}
|
||||
|
||||
crate::define_widget_deref!(Menu);
|
||||
@@ -185,10 +196,11 @@ impl Control for Menu {
|
||||
ui.send_message(MenuItemMessage::close(
|
||||
handle,
|
||||
MessageDirection::ToWidget,
|
||||
true,
|
||||
));
|
||||
// We have to search in popup content too because menu shows its content
|
||||
// in popup and content could be another menu item.
|
||||
stack.push(*item.popup);
|
||||
stack.push(*item.items_panel);
|
||||
}
|
||||
// Continue depth search.
|
||||
stack.extend_from_slice(node.children());
|
||||
@@ -196,6 +208,11 @@ impl Control for Menu {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(WidgetMessage::KeyDown(key_code)) = message.data() {
|
||||
if !message.handled() {
|
||||
message.set_handled(true);
|
||||
keyboard_navigation(ui, *key_code, self, self.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +238,7 @@ impl Control for Menu {
|
||||
'depth_search: while let Some(handle) = stack.pop() {
|
||||
let node = ui.node(handle);
|
||||
if let Some(item) = node.cast::<MenuItem>() {
|
||||
let popup = ui.node(*item.popup);
|
||||
let popup = ui.node(*item.items_panel);
|
||||
if popup.screen_bounds().contains(pos) && popup.is_globally_visible() {
|
||||
// Once we found that we clicked inside some descendant menu item
|
||||
// we can immediately stop search - we don't want to close menu
|
||||
@@ -231,7 +248,7 @@ impl Control for Menu {
|
||||
}
|
||||
// We have to search in popup content too because menu shows its content
|
||||
// in popup and content could be another menu item.
|
||||
stack.push(*item.popup);
|
||||
stack.push(*item.items_panel);
|
||||
}
|
||||
// Continue depth search.
|
||||
stack.extend_from_slice(node.children());
|
||||
@@ -259,6 +276,41 @@ pub enum MenuItemPlacement {
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug, Visit, Reflect, ComponentProvider)]
|
||||
#[doc(hidden)]
|
||||
pub struct ItemsContainer {
|
||||
#[doc(hidden)]
|
||||
pub items: InheritableVariable<Vec<Handle<UiNode>>>,
|
||||
}
|
||||
|
||||
impl Deref for ItemsContainer {
|
||||
type Target = Vec<Handle<UiNode>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.items.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ItemsContainer {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.items.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemsContainer {
|
||||
fn selected_item_index(&self, ui: &UserInterface) -> Option<usize> {
|
||||
for (index, item) in self.items.iter().enumerate() {
|
||||
if let Some(item_ref) = ui.try_get_of_type::<MenuItem>(*item) {
|
||||
if *item_ref.is_selected {
|
||||
return Some(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Menu item is a widget with arbitrary content, that has a "floating" panel (popup) for sub-items if the menu item. This was menu items can form
|
||||
/// arbitrary hierarchies. See [`Menu`] docs for examples.
|
||||
#[derive(Default, Clone, Debug, Visit, Reflect, ComponentProvider)]
|
||||
@@ -266,9 +318,10 @@ pub struct MenuItem {
|
||||
/// Base widget of the menu item.
|
||||
pub widget: Widget,
|
||||
/// Current items of the menu item
|
||||
pub items: InheritableVariable<Vec<Handle<UiNode>>>,
|
||||
#[component(include)]
|
||||
pub items_container: ItemsContainer,
|
||||
/// A handle of a popup that holds the items of the menu item.
|
||||
pub popup: InheritableVariable<Handle<UiNode>>,
|
||||
pub items_panel: InheritableVariable<Handle<UiNode>>,
|
||||
/// A handle of a panel widget that arranges items of the menu item.
|
||||
pub panel: InheritableVariable<Handle<UiNode>>,
|
||||
/// Current placement of the menu item.
|
||||
@@ -277,6 +330,8 @@ pub struct MenuItem {
|
||||
pub clickable_when_not_empty: InheritableVariable<bool>,
|
||||
/// A handle to the decorator of the item.
|
||||
pub decorator: InheritableVariable<Handle<UiNode>>,
|
||||
/// Is this item selected or not.
|
||||
pub is_selected: InheritableVariable<bool>,
|
||||
}
|
||||
|
||||
crate::define_widget_deref!(MenuItem);
|
||||
@@ -289,11 +344,9 @@ crate::define_widget_deref!(MenuItem);
|
||||
fn find_menu(from: Handle<UiNode>, ui: &UserInterface) -> Handle<UiNode> {
|
||||
let mut handle = from;
|
||||
while handle.is_some() {
|
||||
if let Some((_, popup)) = ui.find_component_up::<Popup>(handle) {
|
||||
if let Some((_, panel)) = ui.find_component_up::<MenuItemsPanel>(handle) {
|
||||
// Continue search from parent menu item of popup.
|
||||
handle = popup
|
||||
.user_data_cloned::<Handle<UiNode>>()
|
||||
.unwrap_or_default();
|
||||
handle = panel.parent_menu_item;
|
||||
} else {
|
||||
// Maybe we have Menu as parent for MenuItem.
|
||||
return ui.find_handle_up(handle, &mut |n| n.cast::<Menu>().is_some());
|
||||
@@ -321,18 +374,16 @@ fn is_any_menu_item_contains_point(ui: &UserInterface, pt: Vector2<f32>) -> bool
|
||||
fn close_menu_chain(from: Handle<UiNode>, ui: &UserInterface) {
|
||||
let mut handle = from;
|
||||
while handle.is_some() {
|
||||
let popup_handle = ui.find_handle_up(handle, &mut |n| n.has_component::<Popup>());
|
||||
let popup_handle = ui.find_handle_up(handle, &mut |n| n.has_component::<MenuItemsPanel>());
|
||||
|
||||
if let Some(popup) = ui.node(popup_handle).query_component::<Popup>() {
|
||||
if let Some(panel) = ui.node(popup_handle).query_component::<MenuItemsPanel>() {
|
||||
ui.send_message(PopupMessage::close(
|
||||
popup_handle,
|
||||
MessageDirection::ToWidget,
|
||||
));
|
||||
|
||||
// Continue search from parent menu item of popup.
|
||||
handle = popup
|
||||
.user_data_cloned::<Handle<UiNode>>()
|
||||
.unwrap_or_default();
|
||||
handle = panel.parent_menu_item;
|
||||
} else {
|
||||
// Prevent infinite loops.
|
||||
break;
|
||||
@@ -348,7 +399,7 @@ impl Control for MenuItem {
|
||||
// So we have to remove it manually.
|
||||
sender
|
||||
.send(WidgetMessage::remove(
|
||||
*self.popup,
|
||||
*self.items_panel,
|
||||
MessageDirection::ToWidget,
|
||||
))
|
||||
.unwrap();
|
||||
@@ -374,13 +425,13 @@ impl Control for MenuItem {
|
||||
}
|
||||
WidgetMessage::MouseUp { .. } => {
|
||||
if !message.handled() {
|
||||
if self.items.is_empty() || *self.clickable_when_not_empty {
|
||||
if self.items_container.is_empty() || *self.clickable_when_not_empty {
|
||||
ui.send_message(MenuItemMessage::click(
|
||||
self.handle(),
|
||||
MessageDirection::ToWidget,
|
||||
));
|
||||
}
|
||||
if self.items.is_empty() {
|
||||
if self.items_container.is_empty() {
|
||||
let menu = find_menu(self.parent(), ui);
|
||||
if menu.is_some() {
|
||||
// Deactivate menu if we have one.
|
||||
@@ -416,6 +467,17 @@ impl Control for MenuItem {
|
||||
));
|
||||
}
|
||||
}
|
||||
WidgetMessage::KeyDown(key_code) => {
|
||||
if !message.handled() && *self.is_selected && *key_code == KeyCode::Enter {
|
||||
ui.send_message(MenuItemMessage::click(
|
||||
self.handle,
|
||||
MessageDirection::FromWidget,
|
||||
));
|
||||
let menu = find_menu(self.parent(), ui);
|
||||
ui.send_message(MenuMessage::deactivate(menu, MessageDirection::ToWidget));
|
||||
message.set_handled(true);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if let Some(msg) = message.data::<MenuItemMessage>() {
|
||||
@@ -423,40 +485,60 @@ impl Control for MenuItem {
|
||||
&& message.direction() == MessageDirection::ToWidget
|
||||
{
|
||||
match msg {
|
||||
MenuItemMessage::Select(selected) => {
|
||||
if *self.is_selected != *selected {
|
||||
self.is_selected.set_value_and_mark_modified(*selected);
|
||||
ui.send_message(DecoratorMessage::select(
|
||||
*self.decorator,
|
||||
MessageDirection::ToWidget,
|
||||
*selected,
|
||||
));
|
||||
|
||||
ui.send_message(WidgetMessage::focus(
|
||||
self.handle,
|
||||
MessageDirection::ToWidget,
|
||||
));
|
||||
}
|
||||
}
|
||||
MenuItemMessage::Open => {
|
||||
if !self.items.is_empty() {
|
||||
if !self.items_container.is_empty() {
|
||||
let placement = match *self.placement {
|
||||
MenuItemPlacement::Bottom => Placement::LeftBottom(self.handle),
|
||||
MenuItemPlacement::Right => Placement::RightTop(self.handle),
|
||||
};
|
||||
|
||||
if !*self.is_selected {
|
||||
ui.send_message(MenuItemMessage::select(
|
||||
self.handle,
|
||||
MessageDirection::ToWidget,
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
// Open popup.
|
||||
ui.send_message(PopupMessage::placement(
|
||||
*self.popup,
|
||||
*self.items_panel,
|
||||
MessageDirection::ToWidget,
|
||||
placement,
|
||||
));
|
||||
ui.send_message(PopupMessage::open(
|
||||
*self.popup,
|
||||
*self.items_panel,
|
||||
MessageDirection::ToWidget,
|
||||
));
|
||||
ui.send_message(DecoratorMessage::select(
|
||||
*self.decorator,
|
||||
MessageDirection::ToWidget,
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
MenuItemMessage::Close => {
|
||||
MenuItemMessage::Close { deselect } => {
|
||||
ui.send_message(PopupMessage::close(
|
||||
*self.popup,
|
||||
*self.items_panel,
|
||||
MessageDirection::ToWidget,
|
||||
));
|
||||
ui.send_message(DecoratorMessage::select(
|
||||
*self.decorator,
|
||||
MessageDirection::ToWidget,
|
||||
false,
|
||||
));
|
||||
if *deselect && *self.is_selected {
|
||||
ui.send_message(DecoratorMessage::select(
|
||||
*self.decorator,
|
||||
MessageDirection::ToWidget,
|
||||
false,
|
||||
));
|
||||
}
|
||||
}
|
||||
MenuItemMessage::Click => {}
|
||||
MenuItemMessage::AddItem(item) => {
|
||||
@@ -465,11 +547,13 @@ impl Control for MenuItem {
|
||||
MessageDirection::ToWidget,
|
||||
*self.panel,
|
||||
));
|
||||
self.items.push(*item);
|
||||
self.items_container.push(*item);
|
||||
}
|
||||
MenuItemMessage::RemoveItem(item) => {
|
||||
if let Some(position) = self.items.iter().position(|i| *i == *item) {
|
||||
self.items.remove(position);
|
||||
if let Some(position) =
|
||||
self.items_container.iter().position(|i| *i == *item)
|
||||
{
|
||||
self.items_container.remove(position);
|
||||
|
||||
ui.send_message(WidgetMessage::remove(
|
||||
*item,
|
||||
@@ -478,7 +562,7 @@ impl Control for MenuItem {
|
||||
}
|
||||
}
|
||||
MenuItemMessage::Items(items) => {
|
||||
for ¤t_item in self.items.iter() {
|
||||
for ¤t_item in self.items_container.iter() {
|
||||
ui.send_message(WidgetMessage::remove(
|
||||
current_item,
|
||||
MessageDirection::ToWidget,
|
||||
@@ -493,7 +577,9 @@ impl Control for MenuItem {
|
||||
));
|
||||
}
|
||||
|
||||
self.items.set_value_and_mark_modified(items.clone());
|
||||
self.items_container
|
||||
.items
|
||||
.set_value_and_mark_modified(items.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -513,12 +599,10 @@ impl Control for MenuItem {
|
||||
break;
|
||||
} else {
|
||||
let node = ui.node(handle);
|
||||
if let Some(popup) = node.cast::<Popup>() {
|
||||
if let Some(panel) = node.component_ref::<MenuItemsPanel>() {
|
||||
// Once we found popup in chain, we must extract handle
|
||||
// of parent menu item to continue search.
|
||||
handle = popup
|
||||
.user_data_cloned::<Handle<UiNode>>()
|
||||
.unwrap_or_default();
|
||||
handle = panel.parent_menu_item;
|
||||
} else {
|
||||
handle = node.parent();
|
||||
}
|
||||
@@ -529,6 +613,7 @@ impl Control for MenuItem {
|
||||
ui.send_message(MenuItemMessage::close(
|
||||
self.handle(),
|
||||
MessageDirection::ToWidget,
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -544,14 +629,17 @@ impl Control for MenuItem {
|
||||
// Allow closing "orphaned" menus by clicking outside of them.
|
||||
if let OsEvent::MouseInput { state, .. } = event {
|
||||
if *state == ButtonState::Pressed {
|
||||
if let Some(popup) = ui.node(*self.popup).query_component::<Popup>() {
|
||||
if *popup.is_open {
|
||||
if let Some(panel) = ui
|
||||
.node(*self.items_panel)
|
||||
.query_component::<MenuItemsPanel>()
|
||||
{
|
||||
if *panel.popup.is_open {
|
||||
// Ensure that cursor is outside of any menus.
|
||||
if !is_any_menu_item_contains_point(ui, ui.cursor_position())
|
||||
&& find_menu(self.parent(), ui).is_none()
|
||||
{
|
||||
ui.send_message(PopupMessage::close(
|
||||
*self.popup,
|
||||
*self.items_panel,
|
||||
MessageDirection::ToWidget,
|
||||
));
|
||||
|
||||
@@ -615,6 +703,9 @@ impl MenuBuilder {
|
||||
.with_child(back)
|
||||
.build(),
|
||||
active: false,
|
||||
items: ItemsContainer {
|
||||
items: self.items.into(),
|
||||
},
|
||||
};
|
||||
|
||||
ctx.add_node(UiNode::new(menu))
|
||||
@@ -835,7 +926,11 @@ impl<'a, 'b> MenuItemBuilder<'a, 'b> {
|
||||
})
|
||||
// We'll manually control if popup is either open or closed.
|
||||
.stays_open(true)
|
||||
.build(ctx);
|
||||
.build_popup(ctx);
|
||||
let items_panel = ctx.add_node(UiNode::new(MenuItemsPanel {
|
||||
popup,
|
||||
parent_menu_item: Default::default(),
|
||||
}));
|
||||
|
||||
let menu = MenuItem {
|
||||
widget: self
|
||||
@@ -844,21 +939,179 @@ impl<'a, 'b> MenuItemBuilder<'a, 'b> {
|
||||
.with_preview_messages(true)
|
||||
.with_child(decorator)
|
||||
.build(),
|
||||
popup: popup.into(),
|
||||
items: self.items.into(),
|
||||
items_panel: items_panel.into(),
|
||||
items_container: ItemsContainer {
|
||||
items: self.items.into(),
|
||||
},
|
||||
placement: MenuItemPlacement::Right.into(),
|
||||
panel: panel.into(),
|
||||
clickable_when_not_empty: false.into(),
|
||||
decorator: decorator.into(),
|
||||
is_selected: Default::default(),
|
||||
};
|
||||
|
||||
let handle = ctx.add_node(UiNode::new(menu));
|
||||
|
||||
// "Link" popup with its parent menu item.
|
||||
if let Some(popup) = ctx[popup].cast_mut::<Popup>() {
|
||||
popup.user_data = Some(Arc::new(Mutex::new(handle)));
|
||||
if let Some(popup) = ctx[items_panel].cast_mut::<MenuItemsPanel>() {
|
||||
popup.parent_menu_item = handle;
|
||||
}
|
||||
|
||||
handle
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple wrapper over [`Popup`] widget, that holds the sub-items of a menu item and provides
|
||||
/// an ability for keyboard navigation.
|
||||
#[derive(Default, Clone, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)]
|
||||
#[type_uuid(id = "ad8e9e76-c213-4232-9bab-80ebcabd69fa")]
|
||||
pub struct MenuItemsPanel {
|
||||
#[component(include)]
|
||||
popup: Popup,
|
||||
parent_menu_item: Handle<UiNode>,
|
||||
}
|
||||
|
||||
impl Deref for MenuItemsPanel {
|
||||
type Target = Widget;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.popup.widget
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for MenuItemsPanel {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.popup.widget
|
||||
}
|
||||
}
|
||||
|
||||
impl Control for MenuItemsPanel {
|
||||
fn on_remove(&self, sender: &Sender<UiMessage>) {
|
||||
self.popup.on_remove(sender)
|
||||
}
|
||||
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
|
||||
self.popup.measure_override(ui, available_size)
|
||||
}
|
||||
|
||||
fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
|
||||
self.popup.arrange_override(ui, final_size)
|
||||
}
|
||||
|
||||
fn draw(&self, drawing_context: &mut DrawingContext) {
|
||||
self.popup.draw(drawing_context)
|
||||
}
|
||||
|
||||
fn post_draw(&self, drawing_context: &mut DrawingContext) {
|
||||
self.popup.post_draw(drawing_context)
|
||||
}
|
||||
|
||||
fn update(&mut self, dt: f32, ui: &mut UserInterface) {
|
||||
self.popup.update(dt, ui);
|
||||
}
|
||||
|
||||
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
|
||||
self.popup.handle_routed_message(ui, message);
|
||||
|
||||
if let Some(WidgetMessage::KeyDown(key_code)) = message.data() {
|
||||
if !message.handled() {
|
||||
message.set_handled(true);
|
||||
if let Some(parent_menu_item) = ui.try_get(self.parent_menu_item) {
|
||||
keyboard_navigation(
|
||||
ui,
|
||||
*key_code,
|
||||
parent_menu_item.deref(),
|
||||
self.parent_menu_item,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
|
||||
self.popup.preview_message(ui, message)
|
||||
}
|
||||
|
||||
fn handle_os_event(
|
||||
&mut self,
|
||||
self_handle: Handle<UiNode>,
|
||||
ui: &mut UserInterface,
|
||||
event: &OsEvent,
|
||||
) {
|
||||
self.popup.handle_os_event(self_handle, ui, event)
|
||||
}
|
||||
}
|
||||
|
||||
fn keyboard_navigation(
|
||||
ui: &UserInterface,
|
||||
key_code: KeyCode,
|
||||
parent_menu_item: &dyn Control,
|
||||
parent_menu_item_handle: Handle<UiNode>,
|
||||
) {
|
||||
let Some(items_container) = parent_menu_item
|
||||
.query_component_ref(TypeId::of::<ItemsContainer>())
|
||||
.and_then(|c| c.downcast_ref::<ItemsContainer>())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
match key_code {
|
||||
KeyCode::ArrowLeft => {
|
||||
ui.send_message(MenuItemMessage::close(
|
||||
parent_menu_item_handle,
|
||||
MessageDirection::ToWidget,
|
||||
false,
|
||||
));
|
||||
}
|
||||
KeyCode::ArrowRight => {
|
||||
if let Some(selected_item_index) = items_container.selected_item_index(ui) {
|
||||
let selected_item = items_container.items[selected_item_index];
|
||||
|
||||
ui.send_message(MenuItemMessage::open(
|
||||
selected_item,
|
||||
MessageDirection::ToWidget,
|
||||
));
|
||||
|
||||
if let Some(selected_item_ref) = ui.try_get_of_type::<MenuItem>(selected_item) {
|
||||
if let Some(first_item) = selected_item_ref.items_container.first() {
|
||||
ui.send_message(MenuItemMessage::select(
|
||||
*first_item,
|
||||
MessageDirection::ToWidget,
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::ArrowUp | KeyCode::ArrowDown => {
|
||||
if let Some(selected_item_index) = items_container.selected_item_index(ui) {
|
||||
let new_selection_index = match key_code {
|
||||
KeyCode::ArrowUp => selected_item_index.saturating_sub(1),
|
||||
KeyCode::ArrowDown => selected_item_index.saturating_add(1),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some(new_selection) = items_container.items.get(new_selection_index) {
|
||||
ui.send_message(MenuItemMessage::select(
|
||||
items_container.items[selected_item_index],
|
||||
MessageDirection::ToWidget,
|
||||
false,
|
||||
));
|
||||
ui.send_message(MenuItemMessage::select(
|
||||
*new_selection,
|
||||
MessageDirection::ToWidget,
|
||||
true,
|
||||
));
|
||||
}
|
||||
} else if let Some(first_item) = items_container.items.first() {
|
||||
ui.send_message(MenuItemMessage::select(
|
||||
*first_item,
|
||||
MessageDirection::ToWidget,
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,12 @@ use crate::{
|
||||
inspector::Inspector,
|
||||
key::{HotKeyEditor, KeyBindingEditor},
|
||||
list_view::{ListView, ListViewItem},
|
||||
menu::{Menu, MenuItem},
|
||||
menu::{Menu, MenuItem, MenuItemsPanel},
|
||||
messagebox::MessageBox,
|
||||
nine_patch::NinePatch,
|
||||
numeric::NumericUpDown,
|
||||
path::PathEditor,
|
||||
popup::Popup,
|
||||
progress_bar::ProgressBar,
|
||||
range::RangeEditor,
|
||||
rect::RectEditor,
|
||||
@@ -94,6 +95,7 @@ impl WidgetConstructorContainer {
|
||||
container.add::<ListView>();
|
||||
container.add::<Menu>();
|
||||
container.add::<MenuItem>();
|
||||
container.add::<MenuItemsPanel>();
|
||||
container.add::<MessageBox>();
|
||||
container.add::<NinePatch>();
|
||||
|
||||
@@ -165,6 +167,8 @@ impl WidgetConstructorContainer {
|
||||
|
||||
container.add::<Inspector>();
|
||||
|
||||
container.add::<Popup>();
|
||||
|
||||
container.add::<VecEditor<u8, 2>>();
|
||||
container.add::<VecEditor<i8, 2>>();
|
||||
container.add::<VecEditor<u16, 2>>();
|
||||
|
||||
@@ -98,6 +98,12 @@ pub enum Placement {
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for Placement {
|
||||
fn default() -> Self {
|
||||
Self::LeftTop(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Placement {
|
||||
/// Returns a handle of the node to which this placement corresponds to.
|
||||
pub fn target(&self) -> Handle<UiNode> {
|
||||
@@ -265,7 +271,7 @@ impl Placement {
|
||||
///
|
||||
/// Popup widget can automatically adjust its position to always remain on screen, which is useful for tooltips, dropdown lists,
|
||||
/// etc. To enable this option, use [`PopupBuilder::with_smart_placement`] with `true` as the first argument.
|
||||
#[derive(Clone, Visit, Debug, Reflect, ComponentProvider)]
|
||||
#[derive(Default, Clone, Visit, Debug, Reflect, ComponentProvider)]
|
||||
pub struct Popup {
|
||||
/// Base widget of the popup.
|
||||
pub widget: Widget,
|
||||
|
||||
@@ -249,14 +249,22 @@ impl StackPanelBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Finishes stack panel building.
|
||||
pub fn build_stack_panel(self) -> StackPanel {
|
||||
StackPanel {
|
||||
widget: self.widget_builder.build(),
|
||||
orientation: self.orientation.unwrap_or(Orientation::Vertical).into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Finishes stack panel building and wraps the result in a UI node.
|
||||
pub fn build_node(self) -> UiNode {
|
||||
UiNode::new(self.build_stack_panel())
|
||||
}
|
||||
|
||||
/// Finishes stack panel building and adds the new stack panel widget instance to the user interface and
|
||||
/// returns its handle.
|
||||
pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
|
||||
let stack_panel = StackPanel {
|
||||
widget: self.widget_builder.build(),
|
||||
orientation: self.orientation.unwrap_or(Orientation::Vertical).into(),
|
||||
};
|
||||
|
||||
ctx.add_node(UiNode::new(stack_panel))
|
||||
ctx.add_node(self.build_node())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user