Make GridPlacement's fields non-zero and add accessor functions. (#9486)

# Objective

* There is no way to read the fields of `GridPlacement` once set. 
* Values of `0` for `GridPlacement`'s fields are invalid but can be set.
* A non-zero representation would be half the size.

fixes #9474

## Solution
* Add `get_start`, `get_end` and `get_span` accessor methods.
* Change`GridPlacement`'s constructor functions to panic on arguments of
zero.
* Use non-zero types instead of primitives for `GridPlacement`'s fields.

---

## Changelog

`bevy_ui::ui_node::GridPlacement`:
* Field types have been changed to `Option<NonZeroI16>` and
`Option<NonZeroU16>`. This is because zero values are not valid for
`GridPlacement`. Previously, Taffy interpreted these as auto variants.
* Constructor functions for `GridPlacement` panic on arguments of `0`.
* Added accessor functions: `get_start`, `get_end`, and `get_span`.
These return the inner primitive value (if present) of the respective
fields.

## Migration Guide
`GridPlacement`'s constructor functions no longer accept values of `0`.
Given any argument of `0` they will panic with a `GridPlacementError`.
This commit is contained in:
ickshonpe 2023-08-28 18:21:08 +01:00 committed by GitHub
parent 394e2b0c91
commit a2bd93aedc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 127 additions and 29 deletions

View File

@ -278,8 +278,8 @@ impl From<GridAutoFlow> for taffy::style::GridAutoFlow {
impl From<GridPlacement> for taffy::geometry::Line<taffy::style::GridPlacement> { impl From<GridPlacement> for taffy::geometry::Line<taffy::style::GridPlacement> {
fn from(value: GridPlacement) -> Self { fn from(value: GridPlacement) -> Self {
let span = value.span.unwrap_or(1).max(1); let span = value.get_span().unwrap_or(1);
match (value.start, value.end) { match (value.get_start(), value.get_end()) {
(Some(start), Some(end)) => taffy::geometry::Line { (Some(start), Some(end)) => taffy::geometry::Line {
start: style_helpers::line(start), start: style_helpers::line(start),
end: style_helpers::line(end), end: style_helpers::line(end),

View File

@ -10,7 +10,10 @@ use bevy_render::{
use bevy_transform::prelude::GlobalTransform; use bevy_transform::prelude::GlobalTransform;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::ops::{Div, DivAssign, Mul, MulAssign}; use std::{
num::{NonZeroI16, NonZeroU16},
ops::{Div, DivAssign, Mul, MulAssign},
};
use thiserror::Error; use thiserror::Error;
/// Describes the size of a UI node /// Describes the size of a UI node
@ -1470,100 +1473,145 @@ impl From<RepeatedGridTrack> for Vec<RepeatedGridTrack> {
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Line-based_Placement_with_CSS_Grid> /// <https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Line-based_Placement_with_CSS_Grid>
pub struct GridPlacement { pub struct GridPlacement {
/// The grid line at which the item should start. Lines are 1-indexed. Negative indexes count backwards from the end of the grid. Zero is not a valid index. /// The grid line at which the item should start. Lines are 1-indexed. Negative indexes count backwards from the end of the grid. Zero is not a valid index.
pub(crate) start: Option<i16>, pub(crate) start: Option<NonZeroI16>,
/// How many grid tracks the item should span. Defaults to 1. /// How many grid tracks the item should span. Defaults to 1.
pub(crate) span: Option<u16>, pub(crate) span: Option<NonZeroU16>,
/// The grid line at which the node should end. Lines are 1-indexed. Negative indexes count backwards from the end of the grid. Zero is not a valid index. /// The grid line at which the item should end. Lines are 1-indexed. Negative indexes count backwards from the end of the grid. Zero is not a valid index.
pub(crate) end: Option<i16>, pub(crate) end: Option<NonZeroI16>,
} }
impl GridPlacement { impl GridPlacement {
pub const DEFAULT: Self = Self { pub const DEFAULT: Self = Self {
start: None, start: None,
span: Some(1), span: Some(unsafe { NonZeroU16::new_unchecked(1) }),
end: None, end: None,
}; };
/// Place the grid item automatically (letting the `span` default to `1`). /// Place the grid item automatically (letting the `span` default to `1`).
pub fn auto() -> Self { pub fn auto() -> Self {
Self { Self::DEFAULT
start: None,
end: None,
span: Some(1),
}
} }
/// Place the grid item automatically, specifying how many tracks it should `span`. /// Place the grid item automatically, specifying how many tracks it should `span`.
///
/// # Panics
///
/// Panics if `span` is `0`
pub fn span(span: u16) -> Self { pub fn span(span: u16) -> Self {
Self { Self {
start: None, start: None,
end: None, end: None,
span: Some(span), span: try_into_grid_span(span).expect("Invalid span value of 0."),
} }
} }
/// Place the grid item specifying the `start` grid line (letting the `span` default to `1`). /// Place the grid item specifying the `start` grid line (letting the `span` default to `1`).
///
/// # Panics
///
/// Panics if `start` is `0`
pub fn start(start: i16) -> Self { pub fn start(start: i16) -> Self {
Self { Self {
start: Some(start), start: try_into_grid_index(start).expect("Invalid start value of 0."),
end: None, ..Self::DEFAULT
span: Some(1),
} }
} }
/// Place the grid item specifying the `end` grid line (letting the `span` default to `1`). /// Place the grid item specifying the `end` grid line (letting the `span` default to `1`).
///
/// # Panics
///
/// Panics if `end` is `0`
pub fn end(end: i16) -> Self { pub fn end(end: i16) -> Self {
Self { Self {
start: None, end: try_into_grid_index(end).expect("Invalid end value of 0."),
end: Some(end), ..Self::DEFAULT
span: Some(1),
} }
} }
/// Place the grid item specifying the `start` grid line and how many tracks it should `span`. /// Place the grid item specifying the `start` grid line and how many tracks it should `span`.
///
/// # Panics
///
/// Panics if `start` or `span` is `0`
pub fn start_span(start: i16, span: u16) -> Self { pub fn start_span(start: i16, span: u16) -> Self {
Self { Self {
start: Some(start), start: try_into_grid_index(start).expect("Invalid start value of 0."),
end: None, end: None,
span: Some(span), span: try_into_grid_span(span).expect("Invalid span value of 0."),
} }
} }
/// Place the grid item specifying `start` and `end` grid lines (`span` will be inferred) /// Place the grid item specifying `start` and `end` grid lines (`span` will be inferred)
///
/// # Panics
///
/// Panics if `start` or `end` is `0`
pub fn start_end(start: i16, end: i16) -> Self { pub fn start_end(start: i16, end: i16) -> Self {
Self { Self {
start: Some(start), start: try_into_grid_index(start).expect("Invalid start value of 0."),
end: Some(end), end: try_into_grid_index(end).expect("Invalid end value of 0."),
span: None, span: None,
} }
} }
/// Place the grid item specifying the `end` grid line and how many tracks it should `span`. /// Place the grid item specifying the `end` grid line and how many tracks it should `span`.
///
/// # Panics
///
/// Panics if `end` or `span` is `0`
pub fn end_span(end: i16, span: u16) -> Self { pub fn end_span(end: i16, span: u16) -> Self {
Self { Self {
start: None, start: None,
end: Some(end), end: try_into_grid_index(end).expect("Invalid end value of 0."),
span: Some(span), span: try_into_grid_span(span).expect("Invalid span value of 0."),
} }
} }
/// Mutate the item, setting the `start` grid line /// Mutate the item, setting the `start` grid line
///
/// # Panics
///
/// Panics if `start` is `0`
pub fn set_start(mut self, start: i16) -> Self { pub fn set_start(mut self, start: i16) -> Self {
self.start = Some(start); self.start = try_into_grid_index(start).expect("Invalid start value of 0.");
self self
} }
/// Mutate the item, setting the `end` grid line /// Mutate the item, setting the `end` grid line
///
/// # Panics
///
/// Panics if `end` is `0`
pub fn set_end(mut self, end: i16) -> Self { pub fn set_end(mut self, end: i16) -> Self {
self.end = Some(end); self.end = try_into_grid_index(end).expect("Invalid end value of 0.");
self self
} }
/// Mutate the item, setting the number of tracks the item should `span` /// Mutate the item, setting the number of tracks the item should `span`
///
/// # Panics
///
/// Panics if `span` is `0`
pub fn set_span(mut self, span: u16) -> Self { pub fn set_span(mut self, span: u16) -> Self {
self.span = Some(span); self.span = try_into_grid_span(span).expect("Invalid span value of 0.");
self self
} }
/// Returns the grid line at which the item should start, or `None` if not set.
pub fn get_start(self) -> Option<i16> {
self.start.map(NonZeroI16::get)
}
/// Returns the grid line at which the item should end, or `None` if not set.
pub fn get_end(self) -> Option<i16> {
self.end.map(NonZeroI16::get)
}
/// Returns span for this grid item, or `None` if not set.
pub fn get_span(self) -> Option<u16> {
self.span.map(NonZeroU16::get)
}
} }
impl Default for GridPlacement { impl Default for GridPlacement {
@ -1572,6 +1620,29 @@ impl Default for GridPlacement {
} }
} }
/// Convert an `i16` to `NonZeroI16`, fails on `0` and returns the `InvalidZeroIndex` error.
fn try_into_grid_index(index: i16) -> Result<Option<NonZeroI16>, GridPlacementError> {
Ok(Some(
NonZeroI16::new(index).ok_or(GridPlacementError::InvalidZeroIndex)?,
))
}
/// Convert a `u16` to `NonZeroU16`, fails on `0` and returns the `InvalidZeroSpan` error.
fn try_into_grid_span(span: u16) -> Result<Option<NonZeroU16>, GridPlacementError> {
Ok(Some(
NonZeroU16::new(span).ok_or(GridPlacementError::InvalidZeroSpan)?,
))
}
/// Errors that occur when setting contraints for a `GridPlacement`
#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)]
pub enum GridPlacementError {
#[error("Zero is not a valid grid position")]
InvalidZeroIndex,
#[error("Spans cannot be zero length")]
InvalidZeroSpan,
}
/// The background color of the node /// The background color of the node
/// ///
/// This serves as the "fill" color. /// This serves as the "fill" color.
@ -1719,6 +1790,7 @@ impl Default for ZIndex {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::GridPlacement;
use crate::ValArithmeticError; use crate::ValArithmeticError;
use super::Val; use super::Val;
@ -1867,4 +1939,30 @@ mod tests {
fn default_val_equals_const_default_val() { fn default_val_equals_const_default_val() {
assert_eq!(Val::default(), Val::DEFAULT); assert_eq!(Val::default(), Val::DEFAULT);
} }
#[test]
fn invalid_grid_placement_values() {
assert!(std::panic::catch_unwind(|| GridPlacement::span(0)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::start(0)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::end(0)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::start_end(0, 1)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::start_end(-1, 0)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::start_span(1, 0)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::start_span(0, 1)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::end_span(0, 1)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::end_span(1, 0)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::default().set_start(0)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::default().set_end(0)).is_err());
assert!(std::panic::catch_unwind(|| GridPlacement::default().set_span(0)).is_err());
}
#[test]
fn grid_placement_accessors() {
assert_eq!(GridPlacement::start(5).get_start(), Some(5));
assert_eq!(GridPlacement::end(-4).get_end(), Some(-4));
assert_eq!(GridPlacement::span(2).get_span(), Some(2));
assert_eq!(GridPlacement::start_end(11, 21).get_span(), None);
assert_eq!(GridPlacement::start_span(3, 5).get_end(), None);
assert_eq!(GridPlacement::end_span(-4, 12).get_start(), None);
}
} }