1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use bevy::{
    ecs::{query::QueryFilter, system::SystemParam},
    prelude::*,
};

/// Top-level non-transparent or otherwise interaction blocking Node. All such
/// nodes are marked with this component and no descendants have it attached.
///
/// These nodes block mouse based interaction with the 3D world behind them.
/// These nodes do not block UI interaction: if desired, this must be done via
/// native bevi_ui mechanisms.
#[derive(Component)]
pub(crate) struct InteractionBlocker;

#[derive(SystemParam)]
pub(crate) struct HudNodes<'w, 's, F = With<InteractionBlocker>>
where
    F: QueryFilter + Sync + Send + 'static,
{
    hud: Query<
        'w,
        's,
        (
            &'static GlobalTransform,
            &'static ViewVisibility,
            &'static Node,
        ),
        F,
    >,
}

impl<'w, 's, F> HudNodes<'w, 's, F>
where
    F: QueryFilter + Sync + Send + 'static,
{
    /// See [`Self::relative_position`].
    pub(crate) fn contains_point(&self, point: Vec2) -> bool {
        self.relative_position(point).is_some()
    }

    /// Returns relative position of `point` to the fist Node which contains
    /// the point.
    ///
    /// The returned point is between (0, 0) (top-left corner) and (1, 1)
    /// (bottom-right corner).
    ///
    /// The method relies on [`ViewVisibility`], therefore the results are
    /// accurate with respect to the last rendered frame only iff called before
    /// [`bevy::render::view::VisibilitySystems::VisibilityPropagate`] (during
    /// `PostUpdate` schedule).
    pub(crate) fn relative_position(&self, point: Vec2) -> Option<Vec2> {
        self.hud
            .iter()
            .filter_map(|(box_transform, visibility, node)| {
                if !visibility.get() {
                    return None;
                }

                let box_size = node.size();
                let box_transform: Vec3 = box_transform.translation();
                // GlobalTransform is centered, width/2 to left and to right,
                // same on vertical.
                let box_position = box_transform.xy() - box_size / 2.;
                let relative = (point - box_position) / box_size;
                if relative.cmpge(Vec2::ZERO).all() && relative.cmple(Vec2::ONE).all() {
                    Some(relative)
                } else {
                    None
                }
            })
            .next()
    }
}