use bevy::input::keyboard::KeyCode;
use bevy::input_focus::{InputFocus, InputFocusVisible};
use bevy::picking::events::PointerClick;
use bevy::picking::pointer::PointerButton;
use bevy::prelude::*;
use crate::menu::{MenuPanel, close_menu, show_menu};
use crate::nav::show_focus;
use crate::popover::{Placement, PopoverPanel};
use crate::shortcut::{Modifiers, Shortcut};
#[derive(Component, Clone, Copy, Debug, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Clone, PartialEq)]
pub struct ContextMenu(pub Entity);
#[derive(Component, Clone, Copy, Debug, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Clone, PartialEq)]
pub struct ContextTarget(pub Entity);
impl ContextTarget {
pub fn of(
entity: Entity,
parents: &Query<&ChildOf>,
targets: &Query<&ContextTarget>,
) -> Option<Entity> {
parents
.iter_ancestors(entity)
.find_map(|up| targets.get(up).ok())
.map(|target| target.0)
}
}
#[derive(Clone, Copy, Debug)]
enum At {
Pointer,
Widget(Entity),
}
pub(crate) fn context_on_click(
mut click: On<PointerClick>,
things: Query<&ContextMenu>,
mut commands: Commands,
) {
if click.button != PointerButton::Secondary {
return;
}
let thing = click.entity;
let Ok(&ContextMenu(menu)) = things.get(thing) else {
return;
};
click.propagate(false);
commands.queue(move |world: &mut World| open_context(world, menu, thing, At::Pointer));
}
pub fn context_by_key(
keys: Res<ButtonInput<KeyCode>>,
focus: Res<InputFocus>,
mut shown: ResMut<InputFocusVisible>,
things: Query<&ContextMenu>,
menus: Query<(), With<MenuPanel>>,
parents: Query<&ChildOf>,
mut commands: Commands,
) {
let asked = keys.just_pressed(KeyCode::ContextMenu)
|| Shortcut::new(Modifiers::SHIFT, KeyCode::F10).just_pressed(&keys);
if !asked {
return;
}
let Some(focused) = focus.get() else {
return;
};
let up = std::iter::once(focused).chain(parents.iter_ancestors(focused));
let mut found = None;
for entity in up {
if menus.contains(entity) {
return;
}
if found.is_none()
&& let Ok(&ContextMenu(menu)) = things.get(entity)
{
found = Some((entity, menu));
}
}
let Some((thing, menu)) = found else {
return;
};
show_focus(&mut shown);
commands.queue(move |world: &mut World| open_context(world, menu, thing, At::Widget(focused)));
}
fn open_context(world: &mut World, menu: Entity, thing: Entity, at: At) {
if world.get_entity(menu).is_err()
|| world.get_entity(thing).is_err()
|| world.get::<MenuPanel>(menu).is_none()
{
return;
}
let others: Vec<Entity> = world
.query_filtered::<Entity, (With<MenuPanel>, With<ContextTarget>)>()
.iter(world)
.collect();
for other in others {
close_menu(world, other);
}
if world.get::<PopoverPanel>(menu).is_some() {
close_menu(world, menu);
}
world.entity_mut(menu).insert(ContextTarget(thing));
match at {
At::Pointer => show_menu(world, menu, thing, Placement::AtPointer),
At::Widget(widget) => {
world.entity_mut(menu).insert(ChildOf(widget));
show_menu(world, menu, widget, Placement::Below);
}
}
}