From 44df86f94da3f56ed07491137c0cef1aaaf05381 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 15 Sep 2023 13:45:32 +0100 Subject: [PATCH] Move `Val` into `geometry` (#9818) # Objective `Val`'s natural place is in the `geometry` module with `UiRect`, not in `ui_node` with the components. ## Solution Move `Val` into `geometry`. --- crates/bevy_ui/src/geometry.rs | 258 ++++++++++++++++++++++++++++++++- crates/bevy_ui/src/ui_node.rs | 257 +------------------------------- 2 files changed, 258 insertions(+), 257 deletions(-) diff --git a/crates/bevy_ui/src/geometry.rs b/crates/bevy_ui/src/geometry.rs index 8f940ac0ff2da..f799efe2ade89 100644 --- a/crates/bevy_ui/src/geometry.rs +++ b/crates/bevy_ui/src/geometry.rs @@ -1,5 +1,184 @@ -use crate::Val; +use bevy_math::Vec2; use bevy_reflect::Reflect; +use bevy_reflect::ReflectDeserialize; +use bevy_reflect::ReflectSerialize; +use serde::Deserialize; +use serde::Serialize; +use std::ops::{Div, DivAssign, Mul, MulAssign}; +use thiserror::Error; + +/// Represents the possible value types for layout properties. +/// +/// This enum allows specifying values for various [`Style`](crate::Style) properties in different units, +/// such as logical pixels, percentages, or automatically determined values. +#[derive(Copy, Clone, Debug, Serialize, Deserialize, Reflect)] +#[reflect(PartialEq, Serialize, Deserialize)] +pub enum Val { + /// Automatically determine the value based on the context and other [`Style`](crate::Style) properties. + Auto, + /// Set this value in logical pixels. + Px(f32), + /// Set the value as a percentage of its parent node's length along a specific axis. + /// + /// If the UI node has no parent, the percentage is calculated based on the window's length + /// along the corresponding axis. + /// + /// The chosen axis depends on the [`Style`](crate::Style) field set: + /// * For `flex_basis`, the percentage is relative to the main-axis length determined by the `flex_direction`. + /// * For `gap`, `min_size`, `size`, and `max_size`: + /// - `width` is relative to the parent's width. + /// - `height` is relative to the parent's height. + /// * For `margin`, `padding`, and `border` values: the percentage is relative to the parent node's width. + /// * For positions, `left` and `right` are relative to the parent's width, while `bottom` and `top` are relative to the parent's height. + Percent(f32), + /// Set this value in percent of the viewport width + Vw(f32), + /// Set this value in percent of the viewport height + Vh(f32), + /// Set this value in percent of the viewport's smaller dimension. + VMin(f32), + /// Set this value in percent of the viewport's larger dimension. + VMax(f32), +} + +impl PartialEq for Val { + fn eq(&self, other: &Self) -> bool { + let same_unit = matches!( + (self, other), + (Self::Auto, Self::Auto) + | (Self::Px(_), Self::Px(_)) + | (Self::Percent(_), Self::Percent(_)) + | (Self::Vw(_), Self::Vw(_)) + | (Self::Vh(_), Self::Vh(_)) + | (Self::VMin(_), Self::VMin(_)) + | (Self::VMax(_), Self::VMax(_)) + ); + + let left = match self { + Self::Auto => None, + Self::Px(v) + | Self::Percent(v) + | Self::Vw(v) + | Self::Vh(v) + | Self::VMin(v) + | Self::VMax(v) => Some(v), + }; + + let right = match other { + Self::Auto => None, + Self::Px(v) + | Self::Percent(v) + | Self::Vw(v) + | Self::Vh(v) + | Self::VMin(v) + | Self::VMax(v) => Some(v), + }; + + match (same_unit, left, right) { + (true, a, b) => a == b, + // All zero-value variants are considered equal. + (false, Some(&a), Some(&b)) => a == 0. && b == 0., + _ => false, + } + } +} + +impl Val { + pub const DEFAULT: Self = Self::Auto; + pub const ZERO: Self = Self::Px(0.0); +} + +impl Default for Val { + fn default() -> Self { + Self::DEFAULT + } +} + +impl Mul for Val { + type Output = Val; + + fn mul(self, rhs: f32) -> Self::Output { + match self { + Val::Auto => Val::Auto, + Val::Px(value) => Val::Px(value * rhs), + Val::Percent(value) => Val::Percent(value * rhs), + Val::Vw(value) => Val::Vw(value * rhs), + Val::Vh(value) => Val::Vh(value * rhs), + Val::VMin(value) => Val::VMin(value * rhs), + Val::VMax(value) => Val::VMax(value * rhs), + } + } +} + +impl MulAssign for Val { + fn mul_assign(&mut self, rhs: f32) { + match self { + Val::Auto => {} + Val::Px(value) + | Val::Percent(value) + | Val::Vw(value) + | Val::Vh(value) + | Val::VMin(value) + | Val::VMax(value) => *value *= rhs, + } + } +} + +impl Div for Val { + type Output = Val; + + fn div(self, rhs: f32) -> Self::Output { + match self { + Val::Auto => Val::Auto, + Val::Px(value) => Val::Px(value / rhs), + Val::Percent(value) => Val::Percent(value / rhs), + Val::Vw(value) => Val::Vw(value / rhs), + Val::Vh(value) => Val::Vh(value / rhs), + Val::VMin(value) => Val::VMin(value / rhs), + Val::VMax(value) => Val::VMax(value / rhs), + } + } +} + +impl DivAssign for Val { + fn div_assign(&mut self, rhs: f32) { + match self { + Val::Auto => {} + Val::Px(value) + | Val::Percent(value) + | Val::Vw(value) + | Val::Vh(value) + | Val::VMin(value) + | Val::VMax(value) => *value /= rhs, + } + } +} + +#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)] +pub enum ValArithmeticError { + #[error("the variants of the Vals don't match")] + NonIdenticalVariants, + #[error("the given variant of Val is not evaluateable (non-numeric)")] + NonEvaluateable, +} + +impl Val { + /// Resolves a [`Val`] to its value in logical pixels and returns this as an [`f32`]. + /// Returns a [`ValArithmeticError::NonEvaluateable`] if the [`Val`] is impossible to resolve into a concrete value. + /// + /// **Note:** If a [`Val::Px`] is resolved, it's inner value is returned unchanged. + pub fn resolve(self, parent_size: f32, viewport_size: Vec2) -> Result { + match self { + Val::Percent(value) => Ok(parent_size * value / 100.0), + Val::Px(value) => Ok(value), + Val::Vw(value) => Ok(viewport_size.x * value / 100.0), + Val::Vh(value) => Ok(viewport_size.y * value / 100.0), + Val::VMin(value) => Ok(viewport_size.min_element() * value / 100.0), + Val::VMax(value) => Ok(viewport_size.max_element() * value / 100.0), + Val::Auto => Err(ValArithmeticError::NonEvaluateable), + } + } +} /// A type which is commonly used to define margins, paddings and borders. /// @@ -338,7 +517,82 @@ impl Default for UiRect { #[cfg(test)] mod tests { - use super::*; + use crate::geometry::*; + use bevy_math::vec2; + + #[test] + fn val_evaluate() { + let size = 250.; + let viewport_size = vec2(1000., 500.); + let result = Val::Percent(80.).resolve(size, viewport_size).unwrap(); + + assert_eq!(result, size * 0.8); + } + + #[test] + fn val_resolve_px() { + let size = 250.; + let viewport_size = vec2(1000., 500.); + let result = Val::Px(10.).resolve(size, viewport_size).unwrap(); + + assert_eq!(result, 10.); + } + + #[test] + fn val_resolve_viewport_coords() { + let size = 250.; + let viewport_size = vec2(500., 500.); + + for value in (-10..10).map(|value| value as f32) { + // for a square viewport there should be no difference between `Vw` and `Vh` and between `Vmin` and `Vmax`. + assert_eq!( + Val::Vw(value).resolve(size, viewport_size), + Val::Vh(value).resolve(size, viewport_size) + ); + assert_eq!( + Val::VMin(value).resolve(size, viewport_size), + Val::VMax(value).resolve(size, viewport_size) + ); + assert_eq!( + Val::VMin(value).resolve(size, viewport_size), + Val::Vw(value).resolve(size, viewport_size) + ); + } + + let viewport_size = vec2(1000., 500.); + assert_eq!(Val::Vw(100.).resolve(size, viewport_size).unwrap(), 1000.); + assert_eq!(Val::Vh(100.).resolve(size, viewport_size).unwrap(), 500.); + assert_eq!(Val::Vw(60.).resolve(size, viewport_size).unwrap(), 600.); + assert_eq!(Val::Vh(40.).resolve(size, viewport_size).unwrap(), 200.); + assert_eq!(Val::VMin(50.).resolve(size, viewport_size).unwrap(), 250.); + assert_eq!(Val::VMax(75.).resolve(size, viewport_size).unwrap(), 750.); + } + + #[test] + fn val_auto_is_non_resolveable() { + let size = 250.; + let viewport_size = vec2(1000., 500.); + let resolve_auto = Val::Auto.resolve(size, viewport_size); + + assert_eq!(resolve_auto, Err(ValArithmeticError::NonEvaluateable)); + } + + #[test] + fn val_arithmetic_error_messages() { + assert_eq!( + format!("{}", ValArithmeticError::NonIdenticalVariants), + "the variants of the Vals don't match" + ); + assert_eq!( + format!("{}", ValArithmeticError::NonEvaluateable), + "the given variant of Val is not evaluateable (non-numeric)" + ); + } + + #[test] + fn default_val_equals_const_default_val() { + assert_eq!(Val::default(), Val::DEFAULT); + } #[test] fn uirect_default_equals_const_default() { diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index e0d2ea4ff9acf..14080c82228e0 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1,4 +1,4 @@ -use crate::UiRect; +use crate::{UiRect, Val}; use bevy_asset::Handle; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_math::{Rect, Vec2}; @@ -7,10 +7,7 @@ use bevy_render::{color::Color, texture::Image}; use bevy_transform::prelude::GlobalTransform; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -use std::{ - num::{NonZeroI16, NonZeroU16}, - ops::{Div, DivAssign, Mul, MulAssign}, -}; +use std::num::{NonZeroI16, NonZeroU16}; use thiserror::Error; /// Describes the size of a UI node @@ -78,179 +75,6 @@ impl Default for Node { } } -/// Represents the possible value types for layout properties. -/// -/// This enum allows specifying values for various [`Style`] properties in different units, -/// such as logical pixels, percentages, or automatically determined values. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] -pub enum Val { - /// Automatically determine the value based on the context and other [`Style`] properties. - Auto, - /// Set this value in logical pixels. - Px(f32), - /// Set the value as a percentage of its parent node's length along a specific axis. - /// - /// If the UI node has no parent, the percentage is calculated based on the window's length - /// along the corresponding axis. - /// - /// The chosen axis depends on the `Style` field set: - /// * For `flex_basis`, the percentage is relative to the main-axis length determined by the `flex_direction`. - /// * For `gap`, `min_size`, `size`, and `max_size`: - /// - `width` is relative to the parent's width. - /// - `height` is relative to the parent's height. - /// * For `margin`, `padding`, and `border` values: the percentage is relative to the parent node's width. - /// * For positions, `left` and `right` are relative to the parent's width, while `bottom` and `top` are relative to the parent's height. - Percent(f32), - /// Set this value in percent of the viewport width - Vw(f32), - /// Set this value in percent of the viewport height - Vh(f32), - /// Set this value in percent of the viewport's smaller dimension. - VMin(f32), - /// Set this value in percent of the viewport's larger dimension. - VMax(f32), -} - -impl PartialEq for Val { - fn eq(&self, other: &Self) -> bool { - let same_unit = matches!( - (self, other), - (Self::Auto, Self::Auto) - | (Self::Px(_), Self::Px(_)) - | (Self::Percent(_), Self::Percent(_)) - | (Self::Vw(_), Self::Vw(_)) - | (Self::Vh(_), Self::Vh(_)) - | (Self::VMin(_), Self::VMin(_)) - | (Self::VMax(_), Self::VMax(_)) - ); - - let left = match self { - Self::Auto => None, - Self::Px(v) - | Self::Percent(v) - | Self::Vw(v) - | Self::Vh(v) - | Self::VMin(v) - | Self::VMax(v) => Some(v), - }; - - let right = match other { - Self::Auto => None, - Self::Px(v) - | Self::Percent(v) - | Self::Vw(v) - | Self::Vh(v) - | Self::VMin(v) - | Self::VMax(v) => Some(v), - }; - - match (same_unit, left, right) { - (true, a, b) => a == b, - // All zero-value variants are considered equal. - (false, Some(&a), Some(&b)) => a == 0. && b == 0., - _ => false, - } - } -} - -impl Val { - pub const DEFAULT: Self = Self::Auto; - pub const ZERO: Self = Self::Px(0.0); -} - -impl Default for Val { - fn default() -> Self { - Self::DEFAULT - } -} - -impl Mul for Val { - type Output = Val; - - fn mul(self, rhs: f32) -> Self::Output { - match self { - Val::Auto => Val::Auto, - Val::Px(value) => Val::Px(value * rhs), - Val::Percent(value) => Val::Percent(value * rhs), - Val::Vw(value) => Val::Vw(value * rhs), - Val::Vh(value) => Val::Vh(value * rhs), - Val::VMin(value) => Val::VMin(value * rhs), - Val::VMax(value) => Val::VMax(value * rhs), - } - } -} - -impl MulAssign for Val { - fn mul_assign(&mut self, rhs: f32) { - match self { - Val::Auto => {} - Val::Px(value) - | Val::Percent(value) - | Val::Vw(value) - | Val::Vh(value) - | Val::VMin(value) - | Val::VMax(value) => *value *= rhs, - } - } -} - -impl Div for Val { - type Output = Val; - - fn div(self, rhs: f32) -> Self::Output { - match self { - Val::Auto => Val::Auto, - Val::Px(value) => Val::Px(value / rhs), - Val::Percent(value) => Val::Percent(value / rhs), - Val::Vw(value) => Val::Vw(value / rhs), - Val::Vh(value) => Val::Vh(value / rhs), - Val::VMin(value) => Val::VMin(value / rhs), - Val::VMax(value) => Val::VMax(value / rhs), - } - } -} - -impl DivAssign for Val { - fn div_assign(&mut self, rhs: f32) { - match self { - Val::Auto => {} - Val::Px(value) - | Val::Percent(value) - | Val::Vw(value) - | Val::Vh(value) - | Val::VMin(value) - | Val::VMax(value) => *value /= rhs, - } - } -} - -#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)] -pub enum ValArithmeticError { - #[error("the variants of the Vals don't match")] - NonIdenticalVariants, - #[error("the given variant of Val is not evaluateable (non-numeric)")] - NonEvaluateable, -} - -impl Val { - /// Resolves a [`Val`] to its value in logical pixels and returns this as an [`f32`]. - /// Returns a [`ValArithmeticError::NonEvaluateable`] if the [`Val`] is impossible to resolve into a concrete value. - /// - /// **Note:** If a [`Val::Px`] is resolved, it's inner value is returned unchanged. - pub fn resolve(self, parent_size: f32, viewport_size: Vec2) -> Result { - match self { - Val::Percent(value) => Ok(parent_size * value / 100.0), - Val::Px(value) => Ok(value), - Val::Vw(value) => Ok(viewport_size.x * value / 100.0), - Val::Vh(value) => Ok(viewport_size.y * value / 100.0), - Val::VMin(value) => Ok(viewport_size.min_element() * value / 100.0), - Val::VMax(value) => Ok(viewport_size.max_element() * value / 100.0), - Val::Auto => Err(ValArithmeticError::NonEvaluateable), - } - } -} - /// Describes the style of a UI container node /// /// Node's can be laid out using either Flexbox or CSS Grid Layout.
@@ -1704,84 +1528,7 @@ impl Default for ZIndex { #[cfg(test)] mod tests { - use super::Val; use crate::GridPlacement; - use crate::ValArithmeticError; - use bevy_math::vec2; - - #[test] - fn val_evaluate() { - let size = 250.; - let viewport_size = vec2(1000., 500.); - let result = Val::Percent(80.).resolve(size, viewport_size).unwrap(); - - assert_eq!(result, size * 0.8); - } - - #[test] - fn val_resolve_px() { - let size = 250.; - let viewport_size = vec2(1000., 500.); - let result = Val::Px(10.).resolve(size, viewport_size).unwrap(); - - assert_eq!(result, 10.); - } - - #[test] - fn val_resolve_viewport_coords() { - let size = 250.; - let viewport_size = vec2(500., 500.); - - for value in (-10..10).map(|value| value as f32) { - // for a square viewport there should be no difference between `Vw` and `Vh` and between `Vmin` and `Vmax`. - assert_eq!( - Val::Vw(value).resolve(size, viewport_size), - Val::Vh(value).resolve(size, viewport_size) - ); - assert_eq!( - Val::VMin(value).resolve(size, viewport_size), - Val::VMax(value).resolve(size, viewport_size) - ); - assert_eq!( - Val::VMin(value).resolve(size, viewport_size), - Val::Vw(value).resolve(size, viewport_size) - ); - } - - let viewport_size = vec2(1000., 500.); - assert_eq!(Val::Vw(100.).resolve(size, viewport_size).unwrap(), 1000.); - assert_eq!(Val::Vh(100.).resolve(size, viewport_size).unwrap(), 500.); - assert_eq!(Val::Vw(60.).resolve(size, viewport_size).unwrap(), 600.); - assert_eq!(Val::Vh(40.).resolve(size, viewport_size).unwrap(), 200.); - assert_eq!(Val::VMin(50.).resolve(size, viewport_size).unwrap(), 250.); - assert_eq!(Val::VMax(75.).resolve(size, viewport_size).unwrap(), 750.); - } - - #[test] - fn val_auto_is_non_resolveable() { - let size = 250.; - let viewport_size = vec2(1000., 500.); - let resolve_auto = Val::Auto.resolve(size, viewport_size); - - assert_eq!(resolve_auto, Err(ValArithmeticError::NonEvaluateable)); - } - - #[test] - fn val_arithmetic_error_messages() { - assert_eq!( - format!("{}", ValArithmeticError::NonIdenticalVariants), - "the variants of the Vals don't match" - ); - assert_eq!( - format!("{}", ValArithmeticError::NonEvaluateable), - "the given variant of Val is not evaluateable (non-numeric)" - ); - } - - #[test] - fn default_val_equals_const_default_val() { - assert_eq!(Val::default(), Val::DEFAULT); - } #[test] fn invalid_grid_placement_values() {