#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
#![deny(missing_docs)]
use bevy::{prelude::*, utils::hashbrown::HashSet};
use bevy_picking_core::{
events::{Click, Down, IsPointerEvent, PointerEvent},
pointer::{InputPress, PointerButton, PointerId, PointerLocation},
PickSet,
};
#[derive(Debug, Resource)]
pub struct SelectionSettings {
pub click_nothing_deselect_all: bool,
pub use_multiselect_default_inputs: bool,
}
impl Default for SelectionSettings {
fn default() -> Self {
Self {
click_nothing_deselect_all: true,
use_multiselect_default_inputs: true,
}
}
}
pub struct SelectionPlugin;
impl Plugin for SelectionPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<SelectionSettings>()
.add_event::<PointerEvent<Select>>()
.add_event::<PointerEvent<Deselect>>()
.add_systems(
(
multiselect_events.run_if(|settings: Res<SelectionSettings>| {
settings.use_multiselect_default_inputs
}),
)
.chain()
.in_set(PickSet::ProcessInput),
)
.add_systems(
(
send_selection_events,
bevy_picking_core::event_listening::event_bubbling::<Select>,
bevy_picking_core::event_listening::event_bubbling::<Deselect>,
update_state_from_events,
)
.in_set(PickSet::PostFocus),
);
}
}
#[derive(Debug, Default, Clone, Component, PartialEq, Eq, Reflect)]
pub struct PointerMultiselect {
pub is_pressed: bool,
}
#[derive(Component, Debug, Default, Clone, Reflect)]
pub struct PickSelection {
pub is_selected: bool,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Reflect)]
pub struct Select;
impl IsPointerEvent for Select {}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Reflect)]
pub struct Deselect;
impl IsPointerEvent for Deselect {}
#[derive(Component, Debug, Copy, Clone, Reflect)]
pub struct NoDeselect;
pub fn multiselect_events(
keyboard: Res<Input<KeyCode>>,
mut pointer_query: Query<&mut PointerMultiselect>,
) {
let is_multiselect_pressed = keyboard.any_pressed([
KeyCode::LControl,
KeyCode::RControl,
KeyCode::LShift,
KeyCode::RShift,
]);
for mut multiselect in pointer_query.iter_mut() {
multiselect.is_pressed = is_multiselect_pressed;
}
}
pub fn send_selection_events(
settings: Res<SelectionSettings>,
mut pointer_down: EventReader<PointerEvent<Down>>,
mut presses: EventReader<InputPress>,
mut pointer_click: EventReader<PointerEvent<Click>>,
pointers: Query<(&PointerId, &PointerMultiselect, &PointerLocation)>,
no_deselect: Query<&NoDeselect>,
selectables: Query<(Entity, &PickSelection)>,
mut selections: EventWriter<PointerEvent<Select>>,
mut deselections: EventWriter<PointerEvent<Deselect>>,
) {
let mut pointer_down_list = HashSet::new();
for PointerEvent {
pointer_id,
pointer_location,
target,
event: _,
} in pointer_down.iter()
{
pointer_down_list.insert(pointer_id);
let multiselect = pointers
.iter()
.find_map(|(id, multi, _)| (id == pointer_id).then_some(multi.is_pressed))
.unwrap_or(false);
let target_can_deselect = no_deselect.get(*target).is_err();
if !multiselect && target_can_deselect {
for (entity, selection) in selectables.iter() {
let not_click_target = *target != entity;
if selection.is_selected && not_click_target {
deselections.send(PointerEvent::new(
*pointer_id,
pointer_location.to_owned(),
entity,
Deselect,
))
}
}
}
}
if settings.click_nothing_deselect_all {
for press in presses
.iter()
.filter(|p| p.is_just_down(PointerButton::Primary))
{
let id = press.pointer_id;
let Some((multiselect, location)) = pointers
.iter()
.find_map(|(this_id, multi, location)| {
(*this_id == id)
.then_some(location.location.clone())
.flatten()
.map(|location| (multi.is_pressed, location))
}) else {
continue
};
if !pointer_down_list.contains(&id) && !multiselect {
for (entity, selection) in selectables.iter() {
if selection.is_selected {
deselections.send(PointerEvent::new(id, location.clone(), entity, Deselect))
}
}
}
}
}
for PointerEvent {
pointer_id,
pointer_location,
target,
event: _,
} in pointer_click.iter()
{
let multiselect = pointers
.iter()
.find_map(|(id, multi, _)| id.eq(pointer_id).then_some(multi.is_pressed))
.unwrap_or(false);
if let Ok((entity, selection)) = selectables.get(*target) {
if multiselect {
match selection.is_selected {
true => deselections.send(PointerEvent::new(
*pointer_id,
pointer_location.to_owned(),
entity,
Deselect,
)),
false => selections.send(PointerEvent::new(
*pointer_id,
pointer_location.to_owned(),
entity,
Select,
)),
}
} else if !selection.is_selected {
selections.send(PointerEvent::new(
*pointer_id,
pointer_location.to_owned(),
entity,
Select,
))
}
}
}
}
pub fn update_state_from_events(
mut selectables: Query<&mut PickSelection>,
mut selections: EventReader<PointerEvent<Select>>,
mut deselections: EventReader<PointerEvent<Deselect>>,
) {
for selection in selections.iter() {
if let Ok(mut select_me) = selectables.get_mut(selection.target) {
select_me.is_selected = true;
}
}
for deselection in deselections.iter() {
if let Ok(mut deselect_me) = selectables.get_mut(deselection.target) {
deselect_me.is_selected = false;
}
}
}