use nalgebra_glm::Vec2;
use winit::keyboard::KeyCode;
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(widget_data) = world.ui.get_ui_context_menu_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.input.frame_keys;
for (key, is_pressed) in frame_keys {
if *is_pressed && *key == KeyCode::Escape {
should_close = true;
}
}
let hovered = world
.resources
.retained_ui
.interaction_for_active_mut()
.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 is_inside = match hovered {
Some(hovered_ent) => {
let mut inside =
data.item_entities.contains(&hovered_ent) || hovered_ent == data.popup_entity;
if !inside && use_defs {
inside = is_entity_in_submenu_popups(&data.item_defs, hovered_ent, world);
}
inside
}
None => false,
};
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(widget_data) = world.ui.get_ui_context_menu_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.overlays.active_context_menu = None;
if let Some(cmd_id) = clicked_command {
toggle_checkable_for_command(world, entity, cmd_id);
let tag = lookup_tag(&data.item_defs, cmd_id);
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::ContextMenuItemClicked {
entity,
item_index: cmd_id,
tag,
},
);
}
}
}
fn toggle_checkable_for_command(world: &mut World, menu_entity: freecs::Entity, cmd_id: usize) {
let Some(data) = world.ui.get_ui_context_menu_mut(menu_entity) else {
return;
};
toggle_checkable_in_defs(&mut data.item_defs, cmd_id, &mut world.resources.text.cache);
}
fn toggle_checkable_in_defs(
defs: &mut [crate::ecs::ui::components::ContextMenuItemDef],
cmd_id: usize,
text_cache: &mut crate::ecs::text::resources::TextCache,
) -> bool {
for def in defs.iter_mut() {
let command_id = def.command_id;
if let crate::ecs::ui::components::ContextMenuItemKind::Checkable {
checked,
check_text_slot,
} = &mut def.kind
{
if command_id == Some(cmd_id) {
*checked = !*checked;
text_cache.set_text(*check_text_slot, if *checked { "\u{2713}" } else { "" });
return true;
}
} else if let crate::ecs::ui::components::ContextMenuItemKind::Submenu { children, .. } =
&mut def.kind
&& toggle_checkable_in_defs(children, cmd_id, text_cache)
{
return true;
}
}
false
}
fn lookup_tag(defs: &[crate::ecs::ui::components::ContextMenuItemDef], cmd_id: usize) -> u32 {
for def in defs {
match &def.kind {
crate::ecs::ui::components::ContextMenuItemKind::Action
| crate::ecs::ui::components::ContextMenuItemKind::Checkable { .. }
if def.command_id == Some(cmd_id) =>
{
return def.tag;
}
crate::ecs::ui::components::ContextMenuItemKind::Submenu { children, .. } => {
let tag = lookup_tag(children, cmd_id);
if tag != 0 {
return tag;
}
}
_ => {}
}
}
0
}
fn check_defs_for_click(
world: &World,
defs: &[crate::ecs::ui::components::ContextMenuItemDef],
) -> Option<usize> {
for def in defs {
match &def.kind {
crate::ecs::ui::components::ContextMenuItemKind::Action
| crate::ecs::ui::components::ContextMenuItemKind::Checkable { .. } => {
if 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);
}
}
}
crate::ecs::ui::components::ContextMenuItemKind::Submenu { children, .. } => {
if 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
.interaction_for_active_mut()
.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 = crate::ecs::window::resources::window_scale_factor(world);
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.base_layout.as_mut()
{
window.position = crate::ecs::ui::units::Ab(pos).into();
node.visible = true;
}
}
if let Some(menu_data) = world.ui.get_ui_context_menu_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(menu_data) = world.ui.get_ui_context_menu_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(menu_data) = world.ui.get_ui_context_menu(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);
}
}
}