use bevy_ecs::{
prelude::*,
system::{RunSystemOnce, SystemId, SystemParam},
};
use bevy_math::prelude::*;
use bevy_picking::prelude::*;
use bevy_ui::prelude::*;
use haalka::futures_signals::prelude::*;
#[macro_export]
macro_rules! impl_syncers {
{ $($field:ident: $field_ty:ty),* $(,)? } => {
paste::paste! {
$(
pub fn $field(self, $field: $field_ty) -> Self where Self: ElementWrapper {
self.[<$field _signal>](always($field))
}
pub fn [<$field _signal>](self, [<$field _signal>]: impl Signal<Item = $field_ty> + Send + 'static) -> Self where Self: ElementWrapper {
let syncer = spawn(sync([<$field _signal>], self.$field.clone()));
self.update_raw_el(|raw_el| raw_el.hold_tasks([syncer]))
}
)*
}
};
}
#[derive(Component)]
pub struct AaloOneShotSystem;
pub fn register_system<I: SystemInput + 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
world: &mut World,
system: S,
) -> SystemId<I, O> {
let system = world.register_system(system);
if let Ok(mut entity) = world.get_entity_mut(system.entity()) {
entity.insert(AaloOneShotSystem);
}
system
}
#[macro_export]
macro_rules! signal_or {
($signal:expr) => {
$signal
};
($first:expr, $($rest:expr),+) => {
signal::or($first, signal_or!($($rest),+))
};
}
#[macro_export]
macro_rules! signal_and {
($signal:expr) => {
$signal
};
($first:expr, $($rest:expr),+) => {
signal::and($first, signal_and!($($rest),+))
};
}
pub fn map_bool_signal<T: Copy + Send + Sync + 'static>(
bool: impl Signal<Item = bool>,
t: Mutable<T>,
f: Mutable<T>,
) -> impl Signal<Item = T> {
bool.map_bool_signal(move || t.signal(), move || f.signal())
}
#[allow(dead_code)]
pub fn map_bool_signal_cloned<T: Clone + Send + Sync + 'static>(
bool: Mutable<bool>,
t: Mutable<T>,
f: Mutable<T>,
) -> impl Signal<Item = T> {
bool.signal()
.map_bool_signal(move || t.signal_cloned(), move || f.signal_cloned())
}
#[derive(Component)]
pub struct TooltipTargetPosition(pub Vec2);
pub fn sync_tooltip_position(
expected_tooltip_height: f32,
) -> impl FnOnce(RawHaalkaEl) -> RawHaalkaEl {
move |el| {
el.observe(
|event: On<Pointer<Enter>>, mut inspector_ancestor: InspectorAncestor, mut commands: Commands| {
if let Some(inspector) = inspector_ancestor.get(event.entity)
&& let Ok(mut entity) = commands.get_entity(inspector)
{
entity.try_insert(TooltipTargetPosition(event.event().pointer_location.position));
}
},
)
.observe(
move |move_: On<Pointer<Move>>,
mut move_tooltip_to_position: MoveTooltipToPosition,
mut inspector_ancestor: InspectorAncestor,
mut commands: Commands| {
let entity = move_.entity;
move_tooltip_to_position.move_(entity, move_.pointer_location.position, Some(expected_tooltip_height));
if let Some(inspector) = inspector_ancestor.get(entity)
&& let Ok(mut entity) = commands.get_entity(inspector)
{
entity.try_insert(TooltipTargetPosition(move_.pointer_location.position));
}
},
)
.on_remove(|world, entity| {
world.commands().queue(move |world: &mut World| {
let _ = world.run_system_once(move |tooltips: Query<&TooltipHolder>| {
for TooltipHolder(tooltip) in tooltips.iter() {
let mut lock = tooltip.lock_mut();
if lock.as_ref().map(|tooltip| tooltip.owner) == Some(entity) {
*lock = None;
}
}
});
})
})
}
}
#[derive(Component)]
pub struct InspectorMarker;
#[derive(SystemParam)]
pub struct InspectorAncestor<'w, 's> {
child_ofs: Query<'w, 's, &'static ChildOf>,
entity_inspectors: Query<'w, 's, &'static InspectorMarker>,
cache: Local<'s, Option<Entity>>,
}
impl<'w, 's> InspectorAncestor<'w, 's> {
pub fn get(&mut self, entity: Entity) -> Option<Entity> {
if self.cache.is_none() {
for ancestor in self.child_ofs.iter_ancestors(entity) {
if self.entity_inspectors.contains(ancestor) {
*self.cache = Some(ancestor);
break;
}
}
}
*self.cache
}
}
#[derive(Component)]
pub struct Tooltip;
#[derive(SystemParam)]
pub struct MoveTooltipToPosition<'w, 's> {
childrens: Query<'w, 's, &'static Children>,
nodes: Query<'w, 's, &'static mut Node>,
inspector_ancestor: InspectorAncestor<'w, 's>,
tooltips: Query<'w, 's, &'static Tooltip>,
}
impl<'w, 's> MoveTooltipToPosition<'w, 's> {
pub fn move_(&mut self, entity: Entity, position: Vec2, expected_tooltip_height: Option<f32>) {
if let Some(inspector) = self.inspector_ancestor.get(entity) {
let tooltip = 'block: {
for descendant in self.childrens.iter_descendants(inspector) {
if self.tooltips.contains(descendant) {
break 'block descendant;
}
}
return;
};
if let Ok([inspector_node, mut tooltip_node]) = self.nodes.get_many_mut([inspector, tooltip]) {
let top = if let Val::Px(top) = inspector_node.top { top } else { 0. };
let left = if let Val::Px(left) = inspector_node.left {
left
} else {
0.
};
tooltip_node.top = Val::Px(position.y - top - expected_tooltip_height.unwrap_or_default());
tooltip_node.left = Val::Px(position.x - left);
}
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct TooltipData {
pub owner: Entity,
pub text: String,
}
impl TooltipData {
pub fn new(owner: Entity, text: String) -> Self {
Self { owner, text }
}
}
#[derive(Component, Clone)]
pub struct TooltipHolder(pub Mutable<Option<TooltipData>>);
#[derive(SystemParam)]
pub struct TooltipCache<'w, 's> {
cache: Local<'s, Option<Mutable<Option<TooltipData>>>>,
inspector_ancestor: InspectorAncestor<'w, 's>,
tooltips: Query<'w, 's, &'static TooltipHolder>,
}
impl<'w, 's> TooltipCache<'w, 's> {
pub fn get(&mut self, entity: Entity) -> Option<Mutable<Option<TooltipData>>> {
if self.cache.is_none()
&& let Some(inspector) = self.inspector_ancestor.get(entity)
&& let Ok(TooltipHolder(tooltip)) = self.tooltips.get(inspector).cloned()
{
*self.cache = Some(tooltip);
}
self.cache.clone()
}
}
pub fn is_macos_runtime() -> bool {
cfg_if::cfg_if! {
if #[cfg(not(target_arch = "wasm32"))] {
cfg!(target_os = "macos")
} else {
use web_sys::window;
let win = window().expect("No global `window` exists");
let nav = win.navigator();
if let Ok(platform) = nav.platform() {
if platform.to_lowercase().contains("mac") {
return true;
}
}
if let Ok(ua) = nav.user_agent() {
if ua.to_lowercase().contains("mac os x") {
return true;
}
}
false
}
}
}