Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 9 commits into from
Aug 28, 2023
4 changes: 2 additions & 2 deletions crates/bevy_ui/src/layout/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ impl From<GridAutoFlow> for taffy::style::GridAutoFlow {

impl From<GridPlacement> for taffy::geometry::Line<taffy::style::GridPlacement> {
fn from(value: GridPlacement) -> Self {
let span = value.span.unwrap_or(1).max(1);
match (value.start, value.end) {
let span = value.get_span().unwrap_or(1);
match (value.get_start(), value.get_end()) {
(Some(start), Some(end)) => taffy::geometry::Line {
start: style_helpers::line(start),
end: style_helpers::line(end),
Expand Down
152 changes: 125 additions & 27 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use bevy_render::{
use bevy_transform::prelude::GlobalTransform;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::ops::{Div, DivAssign, Mul, MulAssign};
use std::{
num::{NonZeroI16, NonZeroU16},
ops::{Div, DivAssign, Mul, MulAssign},
};
use thiserror::Error;

/// Describes the size of a UI node
Expand Down Expand Up @@ -1427,100 +1430,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>
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.
pub(crate) start: Option<i16>,
pub(crate) start: Option<NonZeroI16>,
/// How many grid tracks the item should span. Defaults to 1.
pub(crate) span: Option<u16>,
/// 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.
pub(crate) end: Option<i16>,
pub(crate) span: Option<NonZeroU16>,
/// 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<NonZeroI16>,
}

impl GridPlacement {
const DEFAULT: Self = Self {
start: None,
span: Some(1),
span: Some(unsafe { NonZeroU16::new_unchecked(1) }),
end: None,
};

/// Place the grid item automatically (letting the `span` default to `1`).
pub fn auto() -> Self {
Self {
start: None,
end: None,
span: Some(1),
}
Self::DEFAULT
}

/// Place the grid item automatically, specifying how many tracks it should `span`.
///
/// # Panics
///
/// Panics if `span` is `0`
pub fn span(span: u16) -> Self {
Self {
start: 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`).
///
/// # Panics
///
/// Panics if `start` is `0`
pub fn start(start: i16) -> Self {
Self {
start: Some(start),
end: None,
span: Some(1),
start: try_into_grid_index(start).expect("Invalid start value of 0."),
..Self::DEFAULT
}
}

/// 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 {
Self {
start: None,
end: Some(end),
span: Some(1),
end: try_into_grid_index(end).expect("Invalid end value of 0."),
..Self::DEFAULT
}
}

/// 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 {
Self {
start: Some(start),
start: try_into_grid_index(start).expect("Invalid start value of 0."),
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)
///
/// # Panics
///
/// Panics if `start` or `end` is `0`
pub fn start_end(start: i16, end: i16) -> Self {
Self {
start: Some(start),
end: Some(end),
start: try_into_grid_index(start).expect("Invalid start value of 0."),
end: try_into_grid_index(end).expect("Invalid end value of 0."),
span: None,
}
}

/// 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 {
Self {
start: None,
end: Some(end),
span: Some(span),
end: try_into_grid_index(end).expect("Invalid end value of 0."),
span: try_into_grid_span(span).expect("Invalid span value of 0."),
}
}

/// Mutate the item, setting the `start` grid line
///
/// # Panics
///
/// Panics if `start` is `0`
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
}

/// Mutate the item, setting the `end` grid line
///
/// # Panics
///
/// Panics if `end` is `0`
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
}

/// 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 {
self.span = Some(span);
self.span = try_into_grid_span(span).expect("Invalid span value of 0.");
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 {
Expand All @@ -1529,6 +1577,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
///
/// This serves as the "fill" color.
Expand Down Expand Up @@ -1676,6 +1747,7 @@ impl Default for ZIndex {

#[cfg(test)]
mod tests {
use crate::GridPlacement;
use crate::ValArithmeticError;

use super::Val;
Expand Down Expand Up @@ -1824,4 +1896,30 @@ mod tests {
fn default_val_equals_const_default_val() {
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);
}
}