-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split out
context
and placement
modules
- Loading branch information
1 parent
b39abe9
commit 403a612
Showing
3 changed files
with
489 additions
and
449 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
//! TODO | ||
|
||
use bevy_app::{App, PreUpdate}; | ||
#[cfg(feature = "bevy_reflect")] | ||
use bevy_ecs::reflect::ReflectResource; | ||
use bevy_ecs::{ | ||
entity::Entity, | ||
event::{Event, EventReader, EventWriter}, | ||
query::With, | ||
schedule::{common_conditions::on_event, IntoSystemConfigs as _}, | ||
system::{Query, Res, ResMut, Resource}, | ||
}; | ||
use bevy_math::Vec2; | ||
use bevy_render::{ | ||
camera::{Camera, RenderTarget}, | ||
view::Visibility, | ||
}; | ||
use bevy_text::Text; | ||
use bevy_time::Time; | ||
use bevy_ui::{Interaction, UiStack}; | ||
use bevy_window::{PrimaryWindow, Window, WindowRef}; | ||
use tiny_bail::prelude::*; | ||
|
||
use crate::{PrimaryTooltip, Tooltip, TooltipActivation, TooltipEntity, TooltipTransfer}; | ||
|
||
pub(super) fn plugin(app: &mut App) { | ||
app.register_type::<TooltipContext>(); | ||
app.init_resource::<TooltipContext>(); | ||
app.add_event::<HideTooltip>(); | ||
app.add_event::<ShowTooltip>(); | ||
app.add_systems( | ||
PreUpdate, | ||
( | ||
update_tooltip_context, | ||
hide_tooltip.run_if(on_event::<HideTooltip>()), | ||
show_tooltip.run_if(on_event::<ShowTooltip>()), | ||
) | ||
.chain(), | ||
); | ||
} | ||
|
||
/// TODO | ||
#[derive(Resource, Clone, Debug)] | ||
#[cfg_attr( | ||
feature = "bevy_reflect", | ||
derive(bevy_reflect::Reflect), | ||
reflect(Resource) | ||
)] | ||
pub(crate) struct TooltipContext { | ||
/// The current state of the tooltip system. | ||
pub(crate) state: TooltipState, | ||
/// The current or previous target entity being interacted with. | ||
pub(crate) target: Entity, | ||
/// The remaining duration of the current activation delay or transfer timeout (in milliseconds). | ||
timer: u16, | ||
/// The current cursor position or activation point. | ||
pub(crate) cursor_pos: Vec2, | ||
/// The current activation conditions. | ||
activation: TooltipActivation, | ||
/// The current transfer conditions. | ||
transfer: TooltipTransfer, | ||
/// The tooltip container entity. | ||
entity: TooltipEntity, | ||
} | ||
|
||
impl Default for TooltipContext { | ||
fn default() -> Self { | ||
Self { | ||
state: TooltipState::Inactive, | ||
target: Entity::PLACEHOLDER, | ||
timer: 0, | ||
cursor_pos: Vec2::ZERO, | ||
activation: TooltipActivation::IMMEDIATE, | ||
transfer: TooltipTransfer::NONE, | ||
entity: TooltipEntity::Custom(Entity::PLACEHOLDER), | ||
} | ||
} | ||
} | ||
|
||
fn update_tooltip_context( | ||
mut ctx: ResMut<TooltipContext>, | ||
mut hide_tooltip: EventWriter<HideTooltip>, | ||
mut show_tooltip: EventWriter<ShowTooltip>, | ||
primary: Res<PrimaryTooltip>, | ||
time: Res<Time>, | ||
ui_stack: Res<UiStack>, | ||
primary_window_query: Query<Entity, With<PrimaryWindow>>, | ||
window_query: Query<&Window>, | ||
camera_query: Query<&Camera>, | ||
interaction_query: Query<(&Tooltip, &Interaction)>, | ||
) { | ||
let old_active = matches!(ctx.state, TooltipState::Active); | ||
let old_target = ctx.target; | ||
let old_entity = match ctx.entity { | ||
TooltipEntity::Primary(_) => primary.container, | ||
TooltipEntity::Custom(id) => id, | ||
}; | ||
|
||
// TODO: Reconsider whether this is the right way to detect cursor movement. | ||
// Detect cursor movement. | ||
for camera in &camera_query { | ||
let RenderTarget::Window(window) = camera.target else { | ||
continue; | ||
}; | ||
let window = match window { | ||
WindowRef::Primary => cq!(primary_window_query.get_single()), | ||
WindowRef::Entity(id) => id, | ||
}; | ||
let window = c!(window_query.get(window)); | ||
cq!(window.focused); | ||
let cursor_pos = cq!(window.cursor_position()); | ||
|
||
// Reset activation delay on cursor move. | ||
if ctx.cursor_pos != cursor_pos | ||
&& matches!(ctx.state, TooltipState::Delayed) | ||
&& ctx.activation.reset_delay_on_cursor_move | ||
{ | ||
ctx.timer = ctx.activation.delay; | ||
} | ||
|
||
// Dismiss tooltip if cursor has left the activation radius. | ||
if matches!(ctx.state, TooltipState::Active) | ||
&& ctx.cursor_pos.distance_squared(cursor_pos) > ctx.activation.dismiss_radius | ||
{ | ||
ctx.state = TooltipState::Dismissed; | ||
} | ||
|
||
// Update cursor position. | ||
if !matches!(ctx.state, TooltipState::Active) { | ||
ctx.cursor_pos = cursor_pos; | ||
} | ||
|
||
break; | ||
} | ||
|
||
// Tick timer for transfer timeout / activation delay. | ||
if matches!(ctx.state, TooltipState::Inactive | TooltipState::Delayed) { | ||
ctx.timer = ctx.timer.saturating_sub(time.delta().as_millis() as u16); | ||
if matches!(ctx.state, TooltipState::Delayed) && ctx.timer == 0 { | ||
ctx.state = TooltipState::Active; | ||
} | ||
} | ||
|
||
// Find the highest entity in the `UiStack` that has a tooltip and is being interacted with. | ||
let mut found_target = false; | ||
for &entity in ui_stack.uinodes.iter().rev() { | ||
let (tooltip, interaction) = cq!(interaction_query.get(entity)); | ||
match interaction { | ||
Interaction::Pressed => { | ||
ctx.target = entity; | ||
ctx.state = TooltipState::Dismissed; | ||
ctx.transfer = tooltip.transfer; | ||
found_target = true; | ||
break; | ||
} | ||
Interaction::Hovered => (), | ||
Interaction::None => continue, | ||
}; | ||
if !(matches!(ctx.state, TooltipState::Inactive) || ctx.target != entity) { | ||
found_target = true; | ||
break; | ||
} | ||
|
||
// Switch to the new target entity. | ||
ctx.target = entity; | ||
ctx.state = if tooltip.activation.delay == 0 | ||
|| (matches!(ctx.state, TooltipState::Inactive) | ||
&& ctx.timer > 0 | ||
&& ctx.transfer.layer >= tooltip.transfer.layer | ||
&& (matches!((ctx.transfer.group, tooltip.transfer.group), (Some(x), Some(y)) if x == y) | ||
|| ctx.target == entity)) | ||
{ | ||
TooltipState::Active | ||
} else { | ||
TooltipState::Delayed | ||
}; | ||
ctx.timer = tooltip.activation.delay; | ||
ctx.activation = tooltip.activation; | ||
ctx.activation.dismiss_radius *= ctx.activation.dismiss_radius; | ||
ctx.transfer = tooltip.transfer; | ||
ctx.entity = tooltip.entity.clone(); | ||
found_target = true; | ||
break; | ||
} | ||
|
||
// There is no longer a target entity. | ||
if !found_target && !matches!(ctx.state, TooltipState::Inactive) { | ||
ctx.timer = if matches!(ctx.state, TooltipState::Active) || !ctx.transfer.from_active { | ||
ctx.transfer.timeout | ||
} else { | ||
0 | ||
}; | ||
ctx.state = TooltipState::Inactive; | ||
} | ||
|
||
// Update tooltip if it was activated, dismissed, or changed targets. | ||
let new_active = matches!(ctx.state, TooltipState::Active); | ||
if old_active != new_active || old_target != ctx.target { | ||
hide_tooltip.send(HideTooltip { entity: old_entity }); | ||
if new_active { | ||
show_tooltip.send(ShowTooltip); | ||
} | ||
} | ||
} | ||
|
||
/// TODO | ||
#[derive(Copy, Clone, Debug)] | ||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] | ||
pub(crate) enum TooltipState { | ||
/// There is no target entity being interacted with, and no active tooltip. | ||
Inactive, | ||
/// A target entity is being hovered, but its tooltip is not active yet. | ||
Delayed, | ||
/// A target entity is being hovered, and its tooltip is active. | ||
Active, | ||
/// A target entity is being interacted with, but its tooltip has been dismissed. | ||
Dismissed, | ||
} | ||
|
||
/// A buffered event sent when a tooltip should be hidden. | ||
#[derive(Event)] | ||
struct HideTooltip { | ||
entity: Entity, | ||
} | ||
|
||
fn hide_tooltip( | ||
mut hide_tooltip: EventReader<HideTooltip>, | ||
mut visibility_query: Query<&mut Visibility>, | ||
) { | ||
for event in hide_tooltip.read() { | ||
*cq!(visibility_query.get_mut(event.entity)) = Visibility::Hidden; | ||
} | ||
} | ||
|
||
/// A buffered event sent when a tooltip should be shown. | ||
#[derive(Event)] | ||
struct ShowTooltip; | ||
|
||
fn show_tooltip( | ||
mut ctx: ResMut<TooltipContext>, | ||
primary: Res<PrimaryTooltip>, | ||
mut text_query: Query<&mut Text>, | ||
mut visibility_query: Query<&mut Visibility>, | ||
) { | ||
let entity = match &mut ctx.entity { | ||
TooltipEntity::Primary(ref mut text) => { | ||
if let Ok(mut primary_text) = text_query.get_mut(primary.text) { | ||
*primary_text = std::mem::take(text); | ||
} | ||
primary.container | ||
} | ||
TooltipEntity::Custom(id) => *id, | ||
}; | ||
*r!(visibility_query.get_mut(entity)) = Visibility::Visible; | ||
} |
Oops, something went wrong.