use bevy::{
ecs::{
query::{QueryData, QueryFilter, QueryItem},
system::SystemParam,
},
prelude::*,
ui::UiSystem,
};
pub(crate) struct FocusPlugin;
impl Plugin for FocusPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<UiFocus>()
.add_event::<SetFocusEvent>()
.add_systems(PreUpdate, focus_system.after(UiSystem::Focus));
}
}
#[derive(Event)]
pub struct SetFocusEvent(Option<Entity>);
impl SetFocusEvent {
pub fn some(entity: Entity) -> Self {
Self(Some(entity))
}
}
#[derive(SystemParam)]
pub(super) struct FocusedQuery<'w, 's, Q, F = ()>
where
Q: QueryData + Sync + Send + 'static,
F: QueryFilter + Sync + Send + 'static,
{
focus: Res<'w, UiFocus>,
query: Query<'w, 's, Q, F>,
}
impl<'w, 's, Q, F> FocusedQuery<'w, 's, Q, F>
where
Q: QueryData + Sync + Send + 'static,
F: QueryFilter + Sync + Send + 'static,
{
pub(super) fn is_changed(&self) -> bool {
self.focus.is_changed()
}
pub(super) fn get_previous_mut(&mut self) -> Option<QueryItem<'_, Q>> {
self.get_mut(self.focus.previous)
}
pub(super) fn get_current_mut(&mut self) -> Option<QueryItem<'_, Q>> {
self.get_mut(self.focus.current)
}
fn get_mut(&mut self, entity: Option<Entity>) -> Option<QueryItem<'_, Q>> {
match entity {
Some(entity) => match self.query.get_mut(entity) {
Ok(item) => Some(item),
Err(_) => None,
},
None => None,
}
}
}
#[derive(Resource, Default)]
pub(super) struct UiFocus {
previous: Option<Entity>,
current: Option<Entity>,
}
fn focus_system(
mut focus: ResMut<UiFocus>,
mut removals: RemovedComponents<Interaction>,
mouse: Res<ButtonInput<MouseButton>>,
touch: Res<Touches>,
interactions: Query<(Entity, &Interaction), Changed<Interaction>>,
mut events: EventReader<SetFocusEvent>,
) {
let mut current = focus.current;
if let Some(current_entity) = current {
if removals.read().any(|e| e == current_entity) {
current = None;
}
}
if mouse.just_pressed(MouseButton::Left) || touch.any_just_pressed() {
current = None;
}
for (entity, &interaction) in interactions.iter() {
if matches!(interaction, Interaction::Pressed) {
current = Some(entity);
}
}
if let Some(event) = events.read().last() {
current = event.0;
}
if focus.current != current {
focus.previous = focus.current;
focus.current = current;
}
if let Some(previous_entity) = focus.previous {
if removals.read().any(|e| e == previous_entity) {
focus.previous = None;
}
}
}