Skip to main content

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::Mat2;
7use bevy_math::Rot2;
8use bevy_math::Vec2;
9use bevy_reflect::prelude::*;
10use core::ops::Mul;
11
12/// A pair of [`Val`]s used to represent a 2-dimensional size or offset.
13#[derive(Debug, PartialEq, Clone, Copy, Reflect)]
14#[reflect(Default, PartialEq, Debug, Clone)]
15#[cfg_attr(
16    feature = "serialize",
17    derive(serde::Serialize, serde::Deserialize),
18    reflect(Serialize, Deserialize)
19)]
20pub struct Val2 {
21    /// Translate the node along the x-axis.
22    /// `Val::Percent` values are resolved based on the computed width of the Ui Node.
23    /// `Val::Auto` is resolved to `0.`.
24    pub x: Val,
25    /// Translate the node along the y-axis.
26    /// `Val::Percent` values are resolved based on the computed height of the UI Node.
27    /// `Val::Auto` is resolved to `0.`.
28    pub y: Val,
29}
30
31impl Val2 {
32    pub const ZERO: Self = Self {
33        x: Val::ZERO,
34        y: Val::ZERO,
35    };
36
37    /// Creates a new [`Val2`] where both components are in logical pixels
38    pub const fn px(x: f32, y: f32) -> Self {
39        Self {
40            x: Val::Px(x),
41            y: Val::Px(y),
42        }
43    }
44
45    /// Creates a new [`Val2`] where both components are percentage values
46    pub const fn percent(x: f32, y: f32) -> Self {
47        Self {
48            x: Val::Percent(x),
49            y: Val::Percent(y),
50        }
51    }
52
53    /// Creates a new [`Val2`]
54    pub const fn new(x: Val, y: Val) -> Self {
55        Self { x, y }
56    }
57
58    /// Resolves this [`Val2`] from the given `scale_factor`, `parent_size`,
59    /// and `viewport_size`.
60    ///
61    /// Component values of [`Val::Auto`] are resolved to 0.
62    pub fn resolve(&self, scale_factor: f32, base_size: Vec2, viewport_size: Vec2) -> Vec2 {
63        Vec2::new(
64            self.x
65                .resolve(scale_factor, base_size.x, viewport_size)
66                .unwrap_or(0.),
67            self.y
68                .resolve(scale_factor, base_size.y, viewport_size)
69                .unwrap_or(0.),
70        )
71    }
72}
73
74impl Default for Val2 {
75    fn default() -> Self {
76        Self::ZERO
77    }
78}
79
80/// Relative 2D transform for UI nodes
81///
82/// [`UiGlobalTransform`] is automatically inserted whenever [`UiTransform`] is inserted.
83#[derive(Component, Debug, PartialEq, Clone, Copy, Reflect)]
84#[reflect(Component, Default, PartialEq, Debug, Clone)]
85#[cfg_attr(
86    feature = "serialize",
87    derive(serde::Serialize, serde::Deserialize),
88    reflect(Serialize, Deserialize)
89)]
90#[require(UiGlobalTransform)]
91pub struct UiTransform {
92    /// Translate the node.
93    pub translation: Val2,
94    /// Scale the node. A negative value reflects the node in that axis.
95    pub scale: Vec2,
96    /// Rotate the node clockwise.
97    pub rotation: Rot2,
98}
99
100impl UiTransform {
101    pub const IDENTITY: Self = Self {
102        translation: Val2::ZERO,
103        scale: Vec2::ONE,
104        rotation: Rot2::IDENTITY,
105    };
106
107    /// Creates a UI transform representing a rotation.
108    pub const fn from_rotation(rotation: Rot2) -> Self {
109        Self {
110            rotation,
111            ..Self::IDENTITY
112        }
113    }
114
115    /// Creates a UI transform representing a responsive translation.
116    pub const fn from_translation(translation: Val2) -> Self {
117        Self {
118            translation,
119            ..Self::IDENTITY
120        }
121    }
122
123    /// Creates a UI transform representing a scaling.
124    pub const fn from_scale(scale: Vec2) -> Self {
125        Self {
126            scale,
127            ..Self::IDENTITY
128        }
129    }
130
131    /// Resolves the translation from the given `scale_factor`, `base_value`, and `target_size`
132    /// and returns a 2d affine transform from the resolved translation, and the `UiTransform`'s rotation, and scale.
133    pub fn compute_affine(&self, scale_factor: f32, base_size: Vec2, target_size: Vec2) -> Affine2 {
134        Affine2::from_mat2_translation(
135            Mat2::from(self.rotation) * Mat2::from_diagonal(self.scale),
136            self.translation
137                .resolve(scale_factor, base_size, target_size),
138        )
139    }
140}
141
142impl Default for UiTransform {
143    fn default() -> Self {
144        Self::IDENTITY
145    }
146}
147
148/// Absolute 2D transform for UI nodes
149///
150/// [`UiGlobalTransform`]s are updated from [`UiTransform`] and [`Node`](crate::ui_node::Node)
151///  in [`ui_layout_system`](crate::layout::ui_layout_system)
152#[derive(Component, Debug, PartialEq, Clone, Copy, Reflect, Deref)]
153#[reflect(Component, Default, PartialEq, Debug, Clone)]
154#[cfg_attr(
155    feature = "serialize",
156    derive(serde::Serialize, serde::Deserialize),
157    reflect(Serialize, Deserialize)
158)]
159pub struct UiGlobalTransform(Affine2);
160
161impl Default for UiGlobalTransform {
162    fn default() -> Self {
163        Self(Affine2::IDENTITY)
164    }
165}
166
167impl UiGlobalTransform {
168    /// If the transform is invertible returns its inverse.
169    /// Otherwise returns `None`.
170    #[inline]
171    pub fn try_inverse(&self) -> Option<Affine2> {
172        (self.matrix2.determinant() != 0.).then_some(self.inverse())
173    }
174
175    /// Creates a `UiGlobalTransform` from the given 2D translation.
176    #[inline]
177    pub fn from_translation(translation: Vec2) -> Self {
178        Self(Affine2::from_translation(translation))
179    }
180
181    /// Creates a `UiGlobalTransform` from the given 2D translation.
182    #[inline]
183    pub fn from_xy(x: f32, y: f32) -> Self {
184        Self::from_translation(Vec2::new(x, y))
185    }
186
187    /// Creates a `UiGlobalTransform` from the given rotation.
188    #[inline]
189    pub fn from_rotation(rotation: Rot2) -> Self {
190        Self(Affine2::from_mat2(rotation.into()))
191    }
192
193    /// Creates a `UiGlobalTransform` from the given scaling.
194    #[inline]
195    pub fn from_scale(scale: Vec2) -> Self {
196        Self(Affine2::from_scale(scale))
197    }
198
199    /// Extracts scale, angle and translation from self.
200    /// The transform is expected to be non-degenerate and without shearing, or the output will be invalid.
201    #[inline]
202    pub fn to_scale_angle_translation(&self) -> (Vec2, f32, Vec2) {
203        self.0.to_scale_angle_translation()
204    }
205
206    /// Returns the transform as an [`Affine2`]
207    #[inline]
208    pub fn affine(&self) -> Affine2 {
209        self.0
210    }
211}
212
213impl From<Affine2> for UiGlobalTransform {
214    fn from(value: Affine2) -> Self {
215        Self(value)
216    }
217}
218
219impl From<UiGlobalTransform> for Affine2 {
220    fn from(value: UiGlobalTransform) -> Self {
221        value.0
222    }
223}
224
225impl From<&UiGlobalTransform> for Affine2 {
226    fn from(value: &UiGlobalTransform) -> Self {
227        value.0
228    }
229}
230
231impl Mul for UiGlobalTransform {
232    type Output = Self;
233
234    #[inline]
235    fn mul(self, value: Self) -> Self::Output {
236        Self(self.0 * value.0)
237    }
238}
239
240impl Mul<Affine2> for UiGlobalTransform {
241    type Output = Affine2;
242
243    #[inline]
244    fn mul(self, affine2: Affine2) -> Self::Output {
245        self.0 * affine2
246    }
247}
248
249impl Mul<UiGlobalTransform> for Affine2 {
250    type Output = Affine2;
251
252    #[inline]
253    fn mul(self, transform: UiGlobalTransform) -> Self::Output {
254        self * transform.0
255    }
256}
257
258impl Mul<Vec2> for UiGlobalTransform {
259    type Output = Vec2;
260
261    #[inline]
262    fn mul(self, value: Vec2) -> Vec2 {
263        self.transform_point2(value)
264    }
265}