hephae_ui/
root.rs

1//! Defines UI root components.
2//!
3//! See [`UiRoot`] for more information.
4
5use bevy_core_pipeline::core_2d::*;
6use bevy_ecs::{
7    prelude::*,
8    query::{QueryData, QueryItem},
9    system::{StaticSystemParam, SystemParam, SystemParamItem, lifetimeless::Read},
10};
11use bevy_math::prelude::*;
12use bevy_render::prelude::*;
13use bevy_transform::prelude::*;
14
15/// UI root component.
16///
17/// These provide root transforms and available space for UI nodes. For example, [`Camera2dRoot`]
18/// provides a bottom-left transform and physical viewport size as available space.
19///
20/// # Note
21///
22/// Do not add [`Ui`](crate::style::Ui) nodes to the same entity as UI roots. Instead, spawn them as
23/// children entities.
24pub trait UiRoot: Component {
25    /// The parameter required for computing the transform and available space.
26    type Param: SystemParam;
27    /// Necessary neighbor components. Failing to fetch these will make the
28    /// measure output as zero.
29    type Item: QueryData;
30
31    /// Computes the root transform. The returned [`Transform`] should be located at the bottom-left
32    /// vertex of the available space box.
33    fn compute_root_transform(
34        &mut self,
35        param: &mut SystemParamItem<Self::Param>,
36        item: QueryItem<Self::Item>,
37    ) -> (Transform, Vec2);
38}
39
40/// Additional component that, when added to [`UiRoot`]s, will skip rounding the layout for UI
41/// nodes.
42#[derive(Component, Copy, Clone, Default)]
43pub struct UiUnrounded;
44
45#[derive(Component, Copy, Clone, Default)]
46pub(crate) struct UiRootTrns {
47    pub transform: Transform,
48    pub size: Vec2,
49}
50
51pub(crate) fn compute_root_transform<T: UiRoot>(
52    mut param: StaticSystemParam<T::Param>,
53    mut query: Query<(&mut T, &mut UiRootTrns, T::Item)>,
54) {
55    for (mut root, mut output, item) in &mut query {
56        let (transform, size) = root.compute_root_transform(&mut param, item);
57
58        output.bypass_change_detection().transform = transform;
59        output.map_unchanged(|trns| &mut trns.size).set_if_neq(size);
60    }
61}
62
63/// A [`UiRoot`] implementation based on [`Camera2d`]. The available space is the physical viewport
64/// size, scaled as necessary.
65#[derive(Component, Copy, Clone)]
66#[require(Camera2d)]
67pub struct Camera2dRoot {
68    /// Value used to divide the physical viewport size. I.e., `scale: 2.` will make UI nodes twice
69    /// as big.
70    pub scale: f32,
71    /// Z-layer offset for UI nodes.
72    pub offset: f32,
73}
74
75impl Default for Camera2dRoot {
76    #[inline]
77    fn default() -> Self {
78        Self {
79            scale: 1.,
80            offset: -100.,
81        }
82    }
83}
84
85impl UiRoot for Camera2dRoot {
86    type Param = ();
87    type Item = (Read<Camera>, Read<OrthographicProjection>, Has<UiUnrounded>);
88
89    #[inline]
90    fn compute_root_transform(
91        &mut self,
92        _: &mut SystemParamItem<Self::Param>,
93        (camera, projection, is_unrounded): QueryItem<Self::Item>,
94    ) -> (Transform, Vec2) {
95        let area = projection.area;
96        let size = (camera.physical_viewport_size().unwrap_or_default().as_vec2() / self.scale)
97            .map(|value| if !is_unrounded { value.round() } else { value });
98
99        (
100            Transform {
101                // UI transforms originate on bottom-left instead of center. This simplifies projecting points in space to
102                // get the box vertices for UI nodes.
103                translation: area.min.extend(self.offset),
104                rotation: Quat::IDENTITY,
105                // UI nodes assume the physical viewport size as available space, so scale them back to logical size in order
106                // to fit in the camera projection.
107                scale: (area.size() / size).extend(1.),
108            },
109            size,
110        )
111    }
112}