use crate::ecs::ui::state::UiStateTrait as _;
use nalgebra_glm::Vec2;
use winit::keyboard::KeyCode;
use crate::ecs::ui::components::UiWidgetState;
use crate::ecs::world::World;
pub(super) fn handle_context_menu(
world: &mut World,
entity: freecs::Entity,
data: &crate::ecs::ui::components::UiContextMenuData,
) {
if !data.open {
if let Some(UiWidgetState::ContextMenu(widget_data)) =
world.ui.get_ui_widget_state_mut(entity)
{
widget_data.clicked_item = None;
}
return;
}
let use_defs = !data.item_defs.is_empty();
let mut clicked_command = None;
let mut should_close = false;
if use_defs {
clicked_command = check_defs_for_click(world, &data.item_defs);
handle_submenu_hover(world, entity, &data.item_defs);
} else {
for (index, item_entity) in data.item_entities.iter().enumerate() {
if *item_entity == freecs::Entity::default() {
continue;
}
let item_clicked = world
.ui
.get_ui_node_interaction(*item_entity)
.map(|i| i.clicked)
.unwrap_or(false);
if item_clicked {
clicked_command = Some(index);
should_close = true;
break;
}
}
}
if clicked_command.is_some() {
should_close = true;
}
let frame_keys = &world.resources.retained_ui.frame_keys;
for (key, is_pressed) in frame_keys {
if *is_pressed && *key == KeyCode::Escape {
should_close = true;
}
}
let hovered = world.resources.retained_ui.hovered_entity;
let mouse_state = world.resources.input.mouse.state;
let left_just_pressed =
mouse_state.contains(crate::ecs::input::resources::MouseState::LEFT_JUST_PRESSED);
if left_just_pressed && let Some(hovered_ent) = hovered {
let mut is_inside =
data.item_entities.contains(&hovered_ent) || hovered_ent == data.popup_entity;
if !is_inside && use_defs {
is_inside = is_entity_in_submenu_popups(&data.item_defs, hovered_ent, world);
}
if !is_inside {
should_close = true;
}
}
if should_close {
if let Some(node) = world.ui.get_ui_layout_node_mut(data.popup_entity) {
node.visible = false;
}
close_submenu_popups(world, &data.item_defs);
if let Some(UiWidgetState::ContextMenu(widget_data)) =
world.ui.get_ui_widget_state_mut(entity)
{
widget_data.open = false;
widget_data.clicked_item = clicked_command;
close_submenu_defs_state(&mut widget_data.item_defs);
}
world.resources.retained_ui.active_context_menu = None;
if let Some(cmd_id) = clicked_command {
world.resources.retained_ui.frame_events.push(
crate::ecs::ui::resources::UiEvent::ContextMenuItemClicked {
entity,
item_index: cmd_id,
},
);
}
}
}
fn check_defs_for_click(
world: &World,
defs: &[crate::ecs::ui::components::ContextMenuItemDef],
) -> Option<usize> {
for def in defs {
if let crate::ecs::ui::components::ContextMenuItemKind::Action = &def.kind
&& let Some(cmd_id) = def.command_id
{
let clicked = world
.ui
.get_ui_node_interaction(def.row_entity)
.map(|i| i.clicked)
.unwrap_or(false);
if clicked {
return Some(cmd_id);
}
}
if let crate::ecs::ui::components::ContextMenuItemKind::Submenu { children, .. } = &def.kind
&& let Some(cmd_id) = check_defs_for_click(world, children)
{
return Some(cmd_id);
}
}
None
}
fn handle_submenu_hover(
world: &mut World,
menu_entity: freecs::Entity,
defs: &[crate::ecs::ui::components::ContextMenuItemDef],
) {
let hovered = world.resources.retained_ui.hovered_entity;
let mut submenu_indices = Vec::new();
for (index, def) in defs.iter().enumerate() {
if let crate::ecs::ui::components::ContextMenuItemKind::Submenu { .. } = &def.kind {
submenu_indices.push(index);
}
}
for &sub_index in &submenu_indices {
let def = &defs[sub_index];
let row_hovered = hovered == Some(def.row_entity);
if let crate::ecs::ui::components::ContextMenuItemKind::Submenu {
popup_entity,
open,
children,
..
} = &def.kind
{
let child_popup = *popup_entity;
let was_open = *open;
if row_hovered && !was_open {
let row_rect = world
.ui
.get_ui_layout_node(def.row_entity)
.map(|n| n.computed_rect);
if let Some(rect) = row_rect {
let dpi_scale = world.resources.window.cached_scale_factor;
let pos = Vec2::new(rect.max.x, rect.min.y) / dpi_scale;
if let Some(node) = world.ui.get_ui_layout_node_mut(child_popup)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
node.layouts[crate::ecs::ui::state::UiBase::INDEX].as_mut()
{
window.position = crate::ecs::ui::units::Ab(pos).into();
node.visible = true;
}
}
if let Some(UiWidgetState::ContextMenu(menu_data)) =
world.ui.get_ui_widget_state_mut(menu_entity)
&& let Some(item_def) = menu_data.item_defs.get_mut(sub_index)
&& let crate::ecs::ui::components::ContextMenuItemKind::Submenu { open, .. } =
&mut item_def.kind
{
*open = true;
}
} else if !row_hovered && was_open {
let popup_hovered = hovered == Some(child_popup);
let child_item_hovered =
is_entity_in_submenu_popups(children, hovered.unwrap_or_default(), world)
|| popup_hovered;
if !child_item_hovered {
if let Some(node) = world.ui.get_ui_layout_node_mut(child_popup) {
node.visible = false;
}
close_submenu_popups(world, children);
if let Some(UiWidgetState::ContextMenu(menu_data)) =
world.ui.get_ui_widget_state_mut(menu_entity)
&& let Some(item_def) = menu_data.item_defs.get_mut(sub_index)
&& let crate::ecs::ui::components::ContextMenuItemKind::Submenu {
open,
children,
..
} = &mut item_def.kind
{
*open = false;
close_submenu_defs_state(children);
}
}
}
if was_open {
let current_defs = if let Some(UiWidgetState::ContextMenu(menu_data)) =
world.ui.get_ui_widget_state(menu_entity)
{
if let Some(item_def) = menu_data.item_defs.get(sub_index) {
if let crate::ecs::ui::components::ContextMenuItemKind::Submenu {
children,
..
} = &item_def.kind
{
Some(children.clone())
} else {
None
}
} else {
None
}
} else {
None
};
if let Some(child_defs) = current_defs {
handle_submenu_hover(world, menu_entity, &child_defs);
}
}
}
}
}
fn is_entity_in_submenu_popups(
defs: &[crate::ecs::ui::components::ContextMenuItemDef],
target: freecs::Entity,
world: &World,
) -> bool {
for def in defs {
if def.row_entity == target {
return true;
}
if let crate::ecs::ui::components::ContextMenuItemKind::Submenu {
popup_entity,
children,
..
} = &def.kind
{
if *popup_entity == target {
return true;
}
let popup_rect = world
.ui
.get_ui_layout_node(*popup_entity)
.map(|n| n.computed_rect);
if let Some(rect) = popup_rect {
let mouse_pos = world.resources.input.mouse.position;
if rect.contains(mouse_pos) {
return true;
}
}
if is_entity_in_submenu_popups(children, target, world) {
return true;
}
}
}
false
}
pub(super) fn close_submenu_popups(
world: &mut World,
defs: &[crate::ecs::ui::components::ContextMenuItemDef],
) {
for def in defs {
if let crate::ecs::ui::components::ContextMenuItemKind::Submenu {
popup_entity,
children,
..
} = &def.kind
{
if let Some(node) = world.ui.get_ui_layout_node_mut(*popup_entity) {
node.visible = false;
}
close_submenu_popups(world, children);
}
}
}
pub(super) fn close_submenu_defs_state(
defs: &mut [crate::ecs::ui::components::ContextMenuItemDef],
) {
for def in defs.iter_mut() {
if let crate::ecs::ui::components::ContextMenuItemKind::Submenu { open, children, .. } =
&mut def.kind
{
*open = false;
close_submenu_defs_state(children);
}
}
}