diff --git a/crates/bevy_ui/src/layout/convert.rs b/crates/bevy_ui/src/layout/convert.rs index 1f5237a85d..ce424ba44e 100644 --- a/crates/bevy_ui/src/layout/convert.rs +++ b/crates/bevy_ui/src/layout/convert.rs @@ -278,8 +278,8 @@ impl From for taffy::style::GridAutoFlow { impl From for taffy::geometry::Line { 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), diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 435a14b1c0..93bb9a698a 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -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 @@ -1470,100 +1473,145 @@ impl From for Vec { /// 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, + pub(crate) start: Option, /// How many grid tracks the item should span. Defaults to 1. - pub(crate) span: Option, - /// 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, + pub(crate) span: Option, + /// 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, } impl GridPlacement { pub 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 { + 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 { + self.end.map(NonZeroI16::get) + } + + /// Returns span for this grid item, or `None` if not set. + pub fn get_span(self) -> Option { + self.span.map(NonZeroU16::get) + } } 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, 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, 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. @@ -1719,6 +1790,7 @@ impl Default for ZIndex { #[cfg(test)] mod tests { + use crate::GridPlacement; use crate::ValArithmeticError; use super::Val; @@ -1867,4 +1939,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); + } }