bevy_ui/
ui_transform.rs

1use crate::Val;
2use bevy_derive::Deref;
3use bevy_ecs::component::Component;
4use bevy_ecs::prelude::ReflectComponent;
5use bevy_math::Affine2;
6use bevy_math::Rot2;
7use bevy_math::Vec2;
8use bevy_reflect::prelude::*;
9
10/// A pair of [`Val`]s used to represent a 2-dimensional size or offset.
11#[derive(Debug, PartialEq, Clone, Copy, Reflect)]
12#[reflect(Default, PartialEq, Debug, Clone)]
13#[cfg_attr(
14    feature = "serialize",
15    derive(serde::Serialize, serde::Deserialize),
16    reflect(Serialize, Deserialize)
17)]
18pub struct Val2 {
19    /// Translate the node along the x-axis.
20    /// `Val::Percent` values are resolved based on the computed width of the Ui Node.
21    /// `Val::Auto` is resolved to `0.`.
22    pub x: Val,
23    /// Translate the node along the y-axis.
24    /// `Val::Percent` values are resolved based on the computed height of the UI Node.
25    /// `Val::Auto` is resolved to `0.`.
26    pub y: Val,
27}
28
29impl Val2 {
30    pub const ZERO: Self = Self {
31        x: Val::ZERO,
32        y: Val::ZERO,
33    };
34
35    /// Creates a new [`Val2`] where both components are in logical pixels
36    pub const fn px(x: f32, y: f32) -> Self {
37        Self {
38            x: Val::Px(x),
39            y: Val::Px(y),
40        }
41    }
42
43    /// Creates a new [`Val2`] where both components are percentage values
44    pub const fn percent(x: f32, y: f32) -> Self {
45        Self {
46            x: Val::Percent(x),
47            y: Val::Percent(y),
48        }
49    }
50
51    /// Creates a new [`Val2`]
52    pub const fn new(x: Val, y: Val) -> Self {
53        Self { x, y }
54    }
55
56    /// Resolves this [`Val2`] from the given `scale_factor`, `parent_size`,
57    /// and `viewport_size`.
58    ///
59    /// Component values of [`Val::Auto`] are resolved to 0.
60    pub fn resolve(&self, scale_factor: f32, base_size: Vec2, viewport_size: Vec2) -> Vec2 {
61        Vec2::new(
62            self.x
63                .resolve(scale_factor, base_size.x, viewport_size)
64                .unwrap_or(0.),
65            self.y
66                .resolve(scale_factor, base_size.y, viewport_size)
67                .unwrap_or(0.),
68        )
69    }
70}
71
72impl Default for Val2 {
73    fn default() -> Self {
74        Self::ZERO
75    }
76}
77
78/// Relative 2D transform for UI nodes
79///
80/// [`UiGlobalTransform`] is automatically inserted whenever [`UiTransform`] is inserted.
81#[derive(Component, Debug, PartialEq, Clone, Copy, Reflect)]
82#[reflect(Component, Default, PartialEq, Debug, Clone)]
83#[cfg_attr(
84    feature = "serialize",
85    derive(serde::Serialize, serde::Deserialize),
86    reflect(Serialize, Deserialize)
87)]
88#[require(UiGlobalTransform)]
89pub struct UiTransform {
90    /// Translate the node.
91    pub translation: Val2,
92    /// Scale the node. A negative value reflects the node in that axis.
93    pub scale: Vec2,
94    /// Rotate the node clockwise.
95    pub rotation: Rot2,
96}
97
98impl UiTransform {
99    pub const IDENTITY: Self = Self {
100        translation: Val2::ZERO,
101        scale: Vec2::ONE,
102        rotation: Rot2::IDENTITY,
103    };
104
105    /// Creates a UI transform representing a rotation.
106    pub const fn from_rotation(rotation: Rot2) -> Self {
107        Self {
108            rotation,
109            ..Self::IDENTITY
110        }
111    }
112
113    /// Creates a UI transform representing a responsive translation.
114    pub const fn from_translation(translation: Val2) -> Self {
115        Self {
116            translation,
117            ..Self::IDENTITY
118        }
119    }
120
121    /// Creates a UI transform representing a scaling.
122    pub const fn from_scale(scale: Vec2) -> Self {
123        Self {
124            scale,
125            ..Self::IDENTITY
126        }
127    }
128
129    /// Resolves the translation from the given `scale_factor`, `base_value`, and `target_size`
130    /// and returns a 2d affine transform from the resolved translation, and the `UiTransform`'s rotation, and scale.
131    pub fn compute_affine(&self, scale_factor: f32, base_size: Vec2, target_size: Vec2) -> Affine2 {
132        Affine2::from_scale_angle_translation(
133            self.scale,
134            self.rotation.as_radians(),
135            self.translation
136                .resolve(scale_factor, base_size, target_size),
137        )
138    }
139}
140
141impl Default for UiTransform {
142    fn default() -> Self {
143        Self::IDENTITY
144    }
145}
146
147/// Absolute 2D transform for UI nodes
148///
149/// [`UiGlobalTransform`]s are updated from [`UiTransform`] and [`Node`](crate::ui_node::Node)
150///  in [`ui_layout_system`](crate::layout::ui_layout_system)
151#[derive(Component, Debug, PartialEq, Clone, Copy, Reflect, Deref)]
152#[reflect(Component, Default, PartialEq, Debug, Clone)]
153#[cfg_attr(
154    feature = "serialize",
155    derive(serde::Serialize, serde::Deserialize),
156    reflect(Serialize, Deserialize)
157)]
158pub struct UiGlobalTransform(Affine2);
159
160impl Default for UiGlobalTransform {
161    fn default() -> Self {
162        Self(Affine2::IDENTITY)
163    }
164}
165
166impl UiGlobalTransform {
167    /// If the transform is invertible returns its inverse.
168    /// Otherwise returns `None`.
169    #[inline]
170    pub fn try_inverse(&self) -> Option<Affine2> {
171        (self.matrix2.determinant() != 0.).then_some(self.inverse())
172    }
173}
174
175impl From<Affine2> for UiGlobalTransform {
176    fn from(value: Affine2) -> Self {
177        Self(value)
178    }
179}
180
181impl From<UiGlobalTransform> for Affine2 {
182    fn from(value: UiGlobalTransform) -> Self {
183        value.0
184    }
185}
186
187impl From<&UiGlobalTransform> for Affine2 {
188    fn from(value: &UiGlobalTransform) -> Self {
189        value.0
190    }
191}