use crate::{
ui_transform::{UiGlobalTransform, UiTransform},
FocusPolicy, UiRect, Val,
};
use bevy_camera::{visibility::Visibility, Camera, RenderTarget};
use bevy_color::{Alpha, Color};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{prelude::*, system::SystemParam};
use bevy_math::{BVec2, Rect, UVec2, Vec2, Vec4, Vec4Swizzles};
use bevy_reflect::prelude::*;
use bevy_sprite::BorderRect;
use bevy_utils::once;
use bevy_window::{PrimaryWindow, WindowRef};
use core::{f32, num::NonZero};
use derive_more::derive::From;
use smallvec::SmallVec;
use thiserror::Error;
use tracing::warn;
#[derive(Component, Debug, Copy, Clone, PartialEq, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
pub struct ComputedNode {
pub stack_index: u32,
pub size: Vec2,
pub content_size: Vec2,
pub scrollbar_size: Vec2,
pub scroll_position: Vec2,
pub outline_width: f32,
pub outline_offset: f32,
pub unrounded_size: Vec2,
pub border: BorderRect,
pub border_radius: ResolvedBorderRadius,
pub padding: BorderRect,
pub inverse_scale_factor: f32,
}
impl ComputedNode {
#[inline]
pub const fn size(&self) -> Vec2 {
self.size
}
#[inline]
pub const fn content_size(&self) -> Vec2 {
self.content_size
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.size.x <= 0. || self.size.y <= 0.
}
pub const fn stack_index(&self) -> u32 {
self.stack_index
}
#[inline]
pub const fn unrounded_size(&self) -> Vec2 {
self.unrounded_size
}
#[inline]
pub const fn outline_width(&self) -> f32 {
self.outline_width
}
#[inline]
pub const fn outline_offset(&self) -> f32 {
self.outline_offset
}
#[inline]
pub const fn outlined_node_size(&self) -> Vec2 {
let offset = 2. * (self.outline_offset + self.outline_width);
Vec2::new(self.size.x + offset, self.size.y + offset)
}
#[inline]
pub const fn outline_radius(&self) -> ResolvedBorderRadius {
let outer_distance = self.outline_width + self.outline_offset;
const fn compute_radius(radius: f32, outer_distance: f32) -> f32 {
if radius > 0. {
radius + outer_distance
} else {
0.
}
}
ResolvedBorderRadius {
top_left: compute_radius(self.border_radius.top_left, outer_distance),
top_right: compute_radius(self.border_radius.top_right, outer_distance),
bottom_right: compute_radius(self.border_radius.bottom_right, outer_distance),
bottom_left: compute_radius(self.border_radius.bottom_left, outer_distance),
}
}
#[inline]
pub const fn border(&self) -> BorderRect {
self.border
}
#[inline]
pub const fn border_radius(&self) -> ResolvedBorderRadius {
self.border_radius
}
pub fn inner_radius(&self) -> ResolvedBorderRadius {
fn clamp_corner(r: f32, size: Vec2, offset: Vec2) -> f32 {
let s = 0.5 * size + offset;
let sm = s.x.min(s.y);
r.min(sm)
}
let b = Vec4::from((self.border.min_inset, self.border.max_inset));
let s = self.size() - b.xy() - b.zw();
ResolvedBorderRadius {
top_left: clamp_corner(self.border_radius.top_left, s, b.xy()),
top_right: clamp_corner(self.border_radius.top_right, s, b.zy()),
bottom_right: clamp_corner(self.border_radius.bottom_left, s, b.xw()),
bottom_left: clamp_corner(self.border_radius.bottom_right, s, b.zw()),
}
}
#[inline]
pub const fn padding(&self) -> BorderRect {
self.padding
}
#[inline]
pub fn content_inset(&self) -> BorderRect {
let mut content_inset = self.border + self.padding;
content_inset.max_inset += self.scrollbar_size;
content_inset
}
#[inline]
pub const fn inverse_scale_factor(&self) -> f32 {
self.inverse_scale_factor
}
pub fn contains_point(&self, transform: UiGlobalTransform, point: Vec2) -> bool {
let Some(local_point) = transform
.try_inverse()
.map(|transform| transform.transform_point2(point))
else {
return false;
};
let [top, bottom] = if local_point.x < 0. {
[self.border_radius.top_left, self.border_radius.bottom_left]
} else {
[
self.border_radius.top_right,
self.border_radius.bottom_right,
]
};
let r = if local_point.y < 0. { top } else { bottom };
let corner_to_point = local_point.abs() - 0.5 * self.size;
let q = corner_to_point + r;
let l = q.max(Vec2::ZERO).length();
let m = q.max_element().min(0.);
l + m - r < 0.
}
pub fn normalize_point(&self, transform: UiGlobalTransform, point: Vec2) -> Option<Vec2> {
self.size
.cmpgt(Vec2::ZERO)
.all()
.then(|| transform.try_inverse())
.flatten()
.map(|transform| transform.transform_point2(point) / self.size)
}
pub fn resolve_clip_rect(
&self,
overflow: Overflow,
overflow_clip_margin: OverflowClipMargin,
) -> Rect {
let mut clip_rect = Rect::from_center_size(Vec2::ZERO, self.size);
let clip_inset = match overflow_clip_margin.visual_box {
OverflowClipBox::BorderBox => BorderRect::ZERO,
OverflowClipBox::ContentBox => self.content_inset(),
OverflowClipBox::PaddingBox => self.border(),
};
clip_rect.min += clip_inset.min_inset;
clip_rect.max -= clip_inset.max_inset;
if overflow.x == OverflowAxis::Visible {
clip_rect.min.x = -f32::INFINITY;
clip_rect.max.x = f32::INFINITY;
}
if overflow.y == OverflowAxis::Visible {
clip_rect.min.y = -f32::INFINITY;
clip_rect.max.y = f32::INFINITY;
}
clip_rect
}
#[inline]
pub fn border_box(&self) -> Rect {
Rect::from_center_size(Vec2::ZERO, self.size)
}
#[inline]
pub fn padding_box(&self) -> Rect {
let mut out = self.border_box();
out.min += self.border.min_inset;
out.max -= self.border.max_inset;
out
}
#[inline]
pub fn content_box(&self) -> Rect {
let mut out = self.border_box();
let content_inset = self.content_inset();
out.min += content_inset.min_inset;
out.max -= content_inset.max_inset;
out
}
const fn compute_thumb(
gutter_min: f32,
content_length: f32,
gutter_length: f32,
scroll_position: f32,
) -> [f32; 2] {
if content_length <= gutter_length {
return [gutter_min, gutter_min + gutter_length];
}
let thumb_len = gutter_length * gutter_length / content_length;
let thumb_min = gutter_min + scroll_position * gutter_length / content_length;
[thumb_min, thumb_min + thumb_len]
}
pub fn horizontal_scrollbar(&self) -> Option<(Rect, [f32; 2])> {
if self.scrollbar_size.y <= 0. {
return None;
}
let content_inset = self.content_inset();
let half_size = 0.5 * self.size;
let min_x = -half_size.x + content_inset.min_inset.x;
let max_x = half_size.x - content_inset.max_inset.x;
let min_y = half_size.y - content_inset.max_inset.y;
let max_y = min_y + self.scrollbar_size.y;
let gutter = Rect {
min: Vec2::new(min_x, min_y),
max: Vec2::new(max_x, max_y),
};
Some((
gutter,
Self::compute_thumb(
gutter.min.x,
self.content_size.x,
gutter.size().x,
self.scroll_position.x,
),
))
}
pub fn vertical_scrollbar(&self) -> Option<(Rect, [f32; 2])> {
if self.scrollbar_size.x <= 0. {
return None;
}
let content_inset = self.content_inset();
let half_size = 0.5 * self.size;
let min_x = half_size.x - content_inset.max_inset.x;
let max_x = min_x + self.scrollbar_size.x;
let min_y = -half_size.y + content_inset.min_inset.y;
let max_y = half_size.y - content_inset.max_inset.y;
let gutter = Rect {
min: Vec2::new(min_x, min_y),
max: Vec2::new(max_x, max_y),
};
Some((
gutter,
Self::compute_thumb(
gutter.min.y,
self.content_size.y,
gutter.size().y,
self.scroll_position.y,
),
))
}
}
impl ComputedNode {
pub const DEFAULT: Self = Self {
stack_index: 0,
size: Vec2::ZERO,
content_size: Vec2::ZERO,
scrollbar_size: Vec2::ZERO,
scroll_position: Vec2::ZERO,
outline_width: 0.,
outline_offset: 0.,
unrounded_size: Vec2::ZERO,
border_radius: ResolvedBorderRadius::ZERO,
border: BorderRect::ZERO,
padding: BorderRect::ZERO,
inverse_scale_factor: 1.,
};
}
impl Default for ComputedNode {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Component, Debug, Clone, Default, Deref, DerefMut, Reflect)]
#[reflect(Component, Default, Clone)]
pub struct ScrollPosition(pub Vec2);
impl ScrollPosition {
pub const DEFAULT: Self = Self(Vec2::ZERO);
}
impl From<Vec2> for ScrollPosition {
fn from(value: Vec2) -> Self {
Self(value)
}
}
#[derive(Component, Debug, Clone, Default, Deref, DerefMut, Reflect)]
#[reflect(Component, Default, Clone)]
pub struct IgnoreScroll(pub BVec2);
impl From<BVec2> for IgnoreScroll {
fn from(value: BVec2) -> Self {
Self(value)
}
}
#[derive(Component, Clone, PartialEq, Debug, Reflect)]
#[require(
ComputedNode,
ComputedUiTargetCamera,
ComputedUiRenderTargetInfo,
UiTransform,
BackgroundColor,
BorderColor,
FocusPolicy,
ScrollPosition,
Visibility,
ZIndex
)]
#[reflect(Component, Default, PartialEq, Debug, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct Node {
pub display: Display,
pub box_sizing: BoxSizing,
pub position_type: PositionType,
pub overflow: Overflow,
pub scrollbar_width: f32,
pub overflow_clip_margin: OverflowClipMargin,
pub left: Val,
pub right: Val,
pub top: Val,
pub bottom: Val,
pub width: Val,
pub height: Val,
pub min_width: Val,
pub min_height: Val,
pub max_width: Val,
pub max_height: Val,
pub aspect_ratio: Option<f32>,
pub align_items: AlignItems,
pub justify_items: JustifyItems,
pub align_self: AlignSelf,
pub justify_self: JustifySelf,
pub align_content: AlignContent,
pub justify_content: JustifyContent,
pub margin: UiRect,
pub padding: UiRect,
pub border: UiRect,
pub border_radius: BorderRadius,
pub flex_direction: FlexDirection,
pub flex_wrap: FlexWrap,
pub flex_grow: f32,
pub flex_shrink: f32,
pub flex_basis: Val,
pub row_gap: Val,
pub column_gap: Val,
pub grid_auto_flow: GridAutoFlow,
pub grid_template_rows: Vec<RepeatedGridTrack>,
pub grid_template_columns: Vec<RepeatedGridTrack>,
pub grid_auto_rows: Vec<GridTrack>,
pub grid_auto_columns: Vec<GridTrack>,
pub grid_row: GridPlacement,
pub grid_column: GridPlacement,
}
impl Node {
pub const DEFAULT: Self = Self {
display: Display::DEFAULT,
box_sizing: BoxSizing::DEFAULT,
position_type: PositionType::DEFAULT,
left: Val::Auto,
right: Val::Auto,
top: Val::Auto,
bottom: Val::Auto,
flex_direction: FlexDirection::DEFAULT,
flex_wrap: FlexWrap::DEFAULT,
align_items: AlignItems::DEFAULT,
justify_items: JustifyItems::DEFAULT,
align_self: AlignSelf::DEFAULT,
justify_self: JustifySelf::DEFAULT,
align_content: AlignContent::DEFAULT,
justify_content: JustifyContent::DEFAULT,
margin: UiRect::DEFAULT,
padding: UiRect::DEFAULT,
border: UiRect::DEFAULT,
border_radius: BorderRadius::DEFAULT,
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Val::Auto,
width: Val::Auto,
height: Val::Auto,
min_width: Val::Auto,
min_height: Val::Auto,
max_width: Val::Auto,
max_height: Val::Auto,
aspect_ratio: None,
overflow: Overflow::DEFAULT,
overflow_clip_margin: OverflowClipMargin::DEFAULT,
scrollbar_width: 0.,
row_gap: Val::ZERO,
column_gap: Val::ZERO,
grid_auto_flow: GridAutoFlow::DEFAULT,
grid_template_rows: Vec::new(),
grid_template_columns: Vec::new(),
grid_auto_rows: Vec::new(),
grid_auto_columns: Vec::new(),
grid_column: GridPlacement::DEFAULT,
grid_row: GridPlacement::DEFAULT,
};
}
impl Default for Node {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum AlignItems {
Default,
Start,
End,
FlexStart,
FlexEnd,
Center,
Baseline,
Stretch,
}
impl AlignItems {
pub const DEFAULT: Self = Self::Default;
}
impl Default for AlignItems {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum JustifyItems {
Default,
Start,
End,
Center,
Baseline,
Stretch,
}
impl JustifyItems {
pub const DEFAULT: Self = Self::Default;
}
impl Default for JustifyItems {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum AlignSelf {
Auto,
Start,
End,
FlexStart,
FlexEnd,
Center,
Baseline,
Stretch,
}
impl AlignSelf {
pub const DEFAULT: Self = Self::Auto;
}
impl Default for AlignSelf {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum JustifySelf {
Auto,
Start,
End,
Center,
Baseline,
Stretch,
}
impl JustifySelf {
pub const DEFAULT: Self = Self::Auto;
}
impl Default for JustifySelf {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum AlignContent {
Default,
Start,
End,
FlexStart,
FlexEnd,
Center,
Stretch,
SpaceBetween,
SpaceEvenly,
SpaceAround,
}
impl AlignContent {
pub const DEFAULT: Self = Self::Default;
}
impl Default for AlignContent {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum JustifyContent {
Default,
Start,
End,
FlexStart,
FlexEnd,
Center,
Stretch,
SpaceBetween,
SpaceEvenly,
SpaceAround,
}
impl JustifyContent {
pub const DEFAULT: Self = Self::Default;
}
impl Default for JustifyContent {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum Display {
Flex,
Grid,
Block,
None,
}
impl Display {
pub const DEFAULT: Self = Self::Flex;
}
impl Default for Display {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum BoxSizing {
BorderBox,
ContentBox,
}
impl BoxSizing {
pub const DEFAULT: Self = Self::BorderBox;
}
impl Default for BoxSizing {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum FlexDirection {
Row,
Column,
RowReverse,
ColumnReverse,
}
impl FlexDirection {
pub const DEFAULT: Self = Self::Row;
}
impl Default for FlexDirection {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct Overflow {
pub x: OverflowAxis,
pub y: OverflowAxis,
}
impl Overflow {
pub const DEFAULT: Self = Self {
x: OverflowAxis::DEFAULT,
y: OverflowAxis::DEFAULT,
};
pub const fn visible() -> Self {
Self {
x: OverflowAxis::Visible,
y: OverflowAxis::Visible,
}
}
pub const fn clip() -> Self {
Self {
x: OverflowAxis::Clip,
y: OverflowAxis::Clip,
}
}
pub const fn clip_x() -> Self {
Self {
x: OverflowAxis::Clip,
y: OverflowAxis::Visible,
}
}
pub const fn clip_y() -> Self {
Self {
x: OverflowAxis::Visible,
y: OverflowAxis::Clip,
}
}
pub const fn hidden() -> Self {
Self {
x: OverflowAxis::Hidden,
y: OverflowAxis::Hidden,
}
}
pub const fn hidden_x() -> Self {
Self {
x: OverflowAxis::Hidden,
y: OverflowAxis::Visible,
}
}
pub const fn hidden_y() -> Self {
Self {
x: OverflowAxis::Visible,
y: OverflowAxis::Hidden,
}
}
pub const fn is_visible(&self) -> bool {
self.x.is_visible() && self.y.is_visible()
}
pub const fn scroll() -> Self {
Self {
x: OverflowAxis::Scroll,
y: OverflowAxis::Scroll,
}
}
pub const fn scroll_x() -> Self {
Self {
x: OverflowAxis::Scroll,
y: OverflowAxis::Visible,
}
}
pub const fn scroll_y() -> Self {
Self {
x: OverflowAxis::Visible,
y: OverflowAxis::Scroll,
}
}
}
impl Default for Overflow {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum OverflowAxis {
Visible,
Clip,
Hidden,
Scroll,
}
impl OverflowAxis {
pub const DEFAULT: Self = Self::Visible;
pub const fn is_visible(&self) -> bool {
matches!(self, Self::Visible)
}
}
impl Default for OverflowAxis {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct OverflowClipMargin {
pub visual_box: OverflowClipBox,
pub margin: f32,
}
impl OverflowClipMargin {
pub const DEFAULT: Self = Self {
visual_box: OverflowClipBox::PaddingBox,
margin: 0.,
};
pub const fn content_box() -> Self {
Self {
visual_box: OverflowClipBox::ContentBox,
..Self::DEFAULT
}
}
pub const fn padding_box() -> Self {
Self {
visual_box: OverflowClipBox::PaddingBox,
..Self::DEFAULT
}
}
pub const fn border_box() -> Self {
Self {
visual_box: OverflowClipBox::BorderBox,
..Self::DEFAULT
}
}
pub const fn with_margin(mut self, margin: f32) -> Self {
self.margin = margin;
self
}
}
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum OverflowClipBox {
ContentBox,
#[default]
PaddingBox,
BorderBox,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum PositionType {
Relative,
Absolute,
}
impl PositionType {
pub const DEFAULT: Self = Self::Relative;
}
impl Default for PositionType {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum FlexWrap {
NoWrap,
Wrap,
WrapReverse,
}
impl FlexWrap {
pub const DEFAULT: Self = Self::NoWrap;
}
impl Default for FlexWrap {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum GridAutoFlow {
Row,
Column,
RowDense,
ColumnDense,
}
impl GridAutoFlow {
pub const DEFAULT: Self = Self::Row;
}
impl Default for GridAutoFlow {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum MinTrackSizingFunction {
Px(f32),
Percent(f32),
MinContent,
MaxContent,
#[default]
Auto,
VMin(f32),
VMax(f32),
Vh(f32),
Vw(f32),
}
#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum MaxTrackSizingFunction {
Px(f32),
Percent(f32),
MinContent,
MaxContent,
FitContentPx(f32),
FitContentPercent(f32),
#[default]
Auto,
Fraction(f32),
VMin(f32),
VMax(f32),
Vh(f32),
Vw(f32),
}
#[derive(Copy, Clone, PartialEq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct GridTrack {
pub(crate) min_sizing_function: MinTrackSizingFunction,
pub(crate) max_sizing_function: MaxTrackSizingFunction,
}
impl GridTrack {
pub const DEFAULT: Self = Self {
min_sizing_function: MinTrackSizingFunction::Auto,
max_sizing_function: MaxTrackSizingFunction::Auto,
};
pub fn px<T: From<Self>>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Px(value),
max_sizing_function: MaxTrackSizingFunction::Px(value),
}
.into()
}
pub fn percent<T: From<Self>>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Percent(value),
max_sizing_function: MaxTrackSizingFunction::Percent(value),
}
.into()
}
pub fn fr<T: From<Self>>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Auto,
max_sizing_function: MaxTrackSizingFunction::Fraction(value),
}
.into()
}
pub fn flex<T: From<Self>>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Px(0.0),
max_sizing_function: MaxTrackSizingFunction::Fraction(value),
}
.into()
}
pub fn auto<T: From<Self>>() -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Auto,
max_sizing_function: MaxTrackSizingFunction::Auto,
}
.into()
}
pub fn min_content<T: From<Self>>() -> T {
Self {
min_sizing_function: MinTrackSizingFunction::MinContent,
max_sizing_function: MaxTrackSizingFunction::MinContent,
}
.into()
}
pub fn max_content<T: From<Self>>() -> T {
Self {
min_sizing_function: MinTrackSizingFunction::MaxContent,
max_sizing_function: MaxTrackSizingFunction::MaxContent,
}
.into()
}
pub fn fit_content_px<T: From<Self>>(limit: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Auto,
max_sizing_function: MaxTrackSizingFunction::FitContentPx(limit),
}
.into()
}
pub fn fit_content_percent<T: From<Self>>(limit: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Auto,
max_sizing_function: MaxTrackSizingFunction::FitContentPercent(limit),
}
.into()
}
pub fn minmax<T: From<Self>>(min: MinTrackSizingFunction, max: MaxTrackSizingFunction) -> T {
Self {
min_sizing_function: min,
max_sizing_function: max,
}
.into()
}
pub fn vmin<T: From<Self>>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::VMin(value),
max_sizing_function: MaxTrackSizingFunction::VMin(value),
}
.into()
}
pub fn vmax<T: From<Self>>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::VMax(value),
max_sizing_function: MaxTrackSizingFunction::VMax(value),
}
.into()
}
pub fn vh<T: From<Self>>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Vh(value),
max_sizing_function: MaxTrackSizingFunction::Vh(value),
}
.into()
}
pub fn vw<T: From<Self>>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Vw(value),
max_sizing_function: MaxTrackSizingFunction::Vw(value),
}
.into()
}
}
impl Default for GridTrack {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Debug, Reflect, From)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum GridTrackRepetition {
Count(u16),
AutoFill,
AutoFit,
}
impl Default for GridTrackRepetition {
fn default() -> Self {
Self::Count(1)
}
}
impl From<i32> for GridTrackRepetition {
fn from(count: i32) -> Self {
Self::Count(count as u16)
}
}
impl From<usize> for GridTrackRepetition {
fn from(count: usize) -> Self {
Self::Count(count as u16)
}
}
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct RepeatedGridTrack {
pub(crate) repetition: GridTrackRepetition,
pub(crate) tracks: SmallVec<[GridTrack; 1]>,
}
impl RepeatedGridTrack {
pub fn px<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_buf([GridTrack::px(value)]),
}
.into()
}
pub fn percent<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_buf([GridTrack::percent(value)]),
}
.into()
}
pub fn auto<T: From<Self>>(repetition: u16) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::auto()]),
}
.into()
}
pub fn fr<T: From<Self>>(repetition: u16, value: f32) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::fr(value)]),
}
.into()
}
pub fn flex<T: From<Self>>(repetition: u16, value: f32) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::flex(value)]),
}
.into()
}
pub fn min_content<T: From<Self>>(repetition: u16) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::min_content()]),
}
.into()
}
pub fn max_content<T: From<Self>>(repetition: u16) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::max_content()]),
}
.into()
}
pub fn fit_content_px<T: From<Self>>(repetition: u16, limit: f32) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::fit_content_px(limit)]),
}
.into()
}
pub fn fit_content_percent<T: From<Self>>(repetition: u16, limit: f32) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::fit_content_percent(limit)]),
}
.into()
}
pub fn minmax<T: From<Self>>(
repetition: impl Into<GridTrackRepetition>,
min: MinTrackSizingFunction,
max: MaxTrackSizingFunction,
) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_buf([GridTrack::minmax(min, max)]),
}
.into()
}
pub fn vmin<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_buf([GridTrack::vmin(value)]),
}
.into()
}
pub fn vmax<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_buf([GridTrack::vmax(value)]),
}
.into()
}
pub fn vh<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_buf([GridTrack::vh(value)]),
}
.into()
}
pub fn vw<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_buf([GridTrack::vw(value)]),
}
.into()
}
pub fn repeat_many<T: From<Self>>(
repetition: impl Into<GridTrackRepetition>,
tracks: impl Into<Vec<GridTrack>>,
) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_vec(tracks.into()),
}
.into()
}
}
impl Default for RepeatedGridTrack {
fn default() -> Self {
Self {
repetition: Default::default(),
tracks: SmallVec::from_buf([GridTrack::default()]),
}
}
}
impl From<GridTrack> for RepeatedGridTrack {
fn from(track: GridTrack) -> Self {
Self {
repetition: GridTrackRepetition::Count(1),
tracks: SmallVec::from_buf([track]),
}
}
}
impl From<GridTrack> for Vec<GridTrack> {
fn from(track: GridTrack) -> Self {
vec![track]
}
}
impl From<GridTrack> for Vec<RepeatedGridTrack> {
fn from(track: GridTrack) -> Self {
vec![RepeatedGridTrack {
repetition: GridTrackRepetition::Count(1),
tracks: SmallVec::from_buf([track]),
}]
}
}
impl From<RepeatedGridTrack> for Vec<RepeatedGridTrack> {
fn from(track: RepeatedGridTrack) -> Self {
vec![track]
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
#[reflect(Default, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct GridPlacement {
pub(crate) start: Option<NonZero<i16>>,
pub(crate) span: Option<NonZero<u16>>,
pub(crate) end: Option<NonZero<i16>>,
}
impl GridPlacement {
pub const DEFAULT: Self = Self {
start: None,
span: NonZero::<u16>::new(1),
end: None,
};
pub fn auto() -> Self {
Self::DEFAULT
}
pub fn span(span: u16) -> Self {
Self {
start: None,
end: None,
span: try_into_grid_span(span).expect("Invalid span value of 0."),
}
}
pub fn start(start: i16) -> Self {
Self {
start: try_into_grid_index(start).expect("Invalid start value of 0."),
..Self::DEFAULT
}
}
pub fn end(end: i16) -> Self {
Self {
end: try_into_grid_index(end).expect("Invalid end value of 0."),
..Self::DEFAULT
}
}
pub fn start_span(start: i16, span: u16) -> Self {
Self {
start: try_into_grid_index(start).expect("Invalid start value of 0."),
end: None,
span: try_into_grid_span(span).expect("Invalid span value of 0."),
}
}
pub fn start_end(start: i16, end: i16) -> Self {
Self {
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,
}
}
pub fn end_span(end: i16, span: u16) -> Self {
Self {
start: None,
end: try_into_grid_index(end).expect("Invalid end value of 0."),
span: try_into_grid_span(span).expect("Invalid span value of 0."),
}
}
pub fn set_start(mut self, start: i16) -> Self {
self.start = try_into_grid_index(start).expect("Invalid start value of 0.");
self
}
pub fn set_end(mut self, end: i16) -> Self {
self.end = try_into_grid_index(end).expect("Invalid end value of 0.");
self
}
pub fn set_span(mut self, span: u16) -> Self {
self.span = try_into_grid_span(span).expect("Invalid span value of 0.");
self
}
pub fn get_start(self) -> Option<i16> {
self.start.map(NonZero::<i16>::get)
}
pub fn get_end(self) -> Option<i16> {
self.end.map(NonZero::<i16>::get)
}
pub fn get_span(self) -> Option<u16> {
self.span.map(NonZero::<u16>::get)
}
}
impl Default for GridPlacement {
fn default() -> Self {
Self::DEFAULT
}
}
fn try_into_grid_index(index: i16) -> Result<Option<NonZero<i16>>, GridPlacementError> {
Ok(Some(
NonZero::<i16>::new(index).ok_or(GridPlacementError::InvalidZeroIndex)?,
))
}
fn try_into_grid_span(span: u16) -> Result<Option<NonZero<u16>>, GridPlacementError> {
Ok(Some(
NonZero::<u16>::new(span).ok_or(GridPlacementError::InvalidZeroSpan)?,
))
}
#[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,
}
#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct BackgroundColor(pub Color);
impl BackgroundColor {
pub const DEFAULT: Self = Self(Color::NONE);
}
impl Default for BackgroundColor {
fn default() -> Self {
Self::DEFAULT
}
}
impl<T: Into<Color>> From<T> for BackgroundColor {
fn from(color: T) -> Self {
Self(color.into())
}
}
#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct BorderColor {
pub top: Color,
pub right: Color,
pub bottom: Color,
pub left: Color,
}
impl<T: Into<Color>> From<T> for BorderColor {
fn from(color: T) -> Self {
Self::all(color.into())
}
}
impl BorderColor {
pub const DEFAULT: Self = BorderColor {
top: Color::NONE,
right: Color::NONE,
bottom: Color::NONE,
left: Color::NONE,
};
#[inline]
pub fn all(color: impl Into<Color>) -> Self {
let color = color.into();
Self {
top: color,
bottom: color,
left: color,
right: color,
}
}
pub fn set_all(&mut self, color: impl Into<Color>) -> &mut Self {
let color: Color = color.into();
self.top = color;
self.bottom = color;
self.left = color;
self.right = color;
self
}
pub fn is_fully_transparent(&self) -> bool {
self.top.is_fully_transparent()
&& self.bottom.is_fully_transparent()
&& self.left.is_fully_transparent()
&& self.right.is_fully_transparent()
}
}
impl Default for BorderColor {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Component, Copy, Clone, Default, Debug, PartialEq, Reflect)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct Outline {
pub width: Val,
pub offset: Val,
pub color: Color,
}
impl Outline {
pub const fn new(width: Val, offset: Val, color: Color) -> Self {
Self {
width,
offset,
color,
}
}
}
#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
pub struct CalculatedClip {
pub clip: Rect,
}
#[derive(Component)]
pub struct OverrideClip;
#[expect(
rustdoc::redundant_explicit_links,
reason = "To go around the `<code>` limitations, we put the link twice so we're \
sure it's recognized as a markdown link."
)]
#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub struct ZIndex(pub i32);
#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub struct GlobalZIndex(pub i32);
#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
#[reflect(PartialEq, Default, Debug, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct BorderRadius {
pub top_left: Val,
pub top_right: Val,
pub bottom_right: Val,
pub bottom_left: Val,
}
impl Default for BorderRadius {
fn default() -> Self {
Self::DEFAULT
}
}
impl BorderRadius {
pub const DEFAULT: Self = Self::ZERO;
pub const ZERO: Self = Self::all(Val::Px(0.));
pub const MAX: Self = Self::all(Val::Px(f32::MAX));
#[inline]
pub const fn all(radius: Val) -> Self {
Self {
top_left: radius,
top_right: radius,
bottom_left: radius,
bottom_right: radius,
}
}
#[inline]
pub const fn new(top_left: Val, top_right: Val, bottom_right: Val, bottom_left: Val) -> Self {
Self {
top_left,
top_right,
bottom_right,
bottom_left,
}
}
#[inline]
pub const fn px(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
Self {
top_left: Val::Px(top_left),
top_right: Val::Px(top_right),
bottom_right: Val::Px(bottom_right),
bottom_left: Val::Px(bottom_left),
}
}
#[inline]
pub const fn percent(
top_left: f32,
top_right: f32,
bottom_right: f32,
bottom_left: f32,
) -> Self {
Self {
top_left: Val::Percent(top_left),
top_right: Val::Percent(top_right),
bottom_right: Val::Percent(bottom_right),
bottom_left: Val::Percent(bottom_left),
}
}
#[inline]
pub const fn top_left(radius: Val) -> Self {
Self {
top_left: radius,
..Self::DEFAULT
}
}
#[inline]
pub const fn top_right(radius: Val) -> Self {
Self {
top_right: radius,
..Self::DEFAULT
}
}
#[inline]
pub const fn bottom_right(radius: Val) -> Self {
Self {
bottom_right: radius,
..Self::DEFAULT
}
}
#[inline]
pub const fn bottom_left(radius: Val) -> Self {
Self {
bottom_left: radius,
..Self::DEFAULT
}
}
#[inline]
pub const fn left(radius: Val) -> Self {
Self {
top_left: radius,
bottom_left: radius,
..Self::DEFAULT
}
}
#[inline]
pub const fn right(radius: Val) -> Self {
Self {
top_right: radius,
bottom_right: radius,
..Self::DEFAULT
}
}
#[inline]
pub const fn top(radius: Val) -> Self {
Self {
top_left: radius,
top_right: radius,
..Self::DEFAULT
}
}
#[inline]
pub const fn bottom(radius: Val) -> Self {
Self {
bottom_left: radius,
bottom_right: radius,
..Self::DEFAULT
}
}
#[inline]
pub const fn with_top_left(mut self, radius: Val) -> Self {
self.top_left = radius;
self
}
#[inline]
pub const fn with_top_right(mut self, radius: Val) -> Self {
self.top_right = radius;
self
}
#[inline]
pub const fn with_bottom_right(mut self, radius: Val) -> Self {
self.bottom_right = radius;
self
}
#[inline]
pub const fn with_bottom_left(mut self, radius: Val) -> Self {
self.bottom_left = radius;
self
}
#[inline]
pub const fn with_left(mut self, radius: Val) -> Self {
self.top_left = radius;
self.bottom_left = radius;
self
}
#[inline]
pub const fn with_right(mut self, radius: Val) -> Self {
self.top_right = radius;
self.bottom_right = radius;
self
}
#[inline]
pub const fn with_top(mut self, radius: Val) -> Self {
self.top_left = radius;
self.top_right = radius;
self
}
#[inline]
pub const fn with_bottom(mut self, radius: Val) -> Self {
self.bottom_left = radius;
self.bottom_right = radius;
self
}
pub const fn resolve_single_corner(
radius: Val,
scale_factor: f32,
min_length: f32,
viewport_size: Vec2,
) -> f32 {
if let Ok(radius) = radius.resolve(scale_factor, min_length, viewport_size) {
radius.clamp(0., 0.5 * min_length)
} else {
0.
}
}
pub const fn resolve(
&self,
scale_factor: f32,
node_size: Vec2,
viewport_size: Vec2,
) -> ResolvedBorderRadius {
let length = node_size.x.min(node_size.y);
ResolvedBorderRadius {
top_left: Self::resolve_single_corner(
self.top_left,
scale_factor,
length,
viewport_size,
),
top_right: Self::resolve_single_corner(
self.top_right,
scale_factor,
length,
viewport_size,
),
bottom_left: Self::resolve_single_corner(
self.bottom_left,
scale_factor,
length,
viewport_size,
),
bottom_right: Self::resolve_single_corner(
self.bottom_right,
scale_factor,
length,
viewport_size,
),
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
#[reflect(Clone, PartialEq, Default)]
pub struct ResolvedBorderRadius {
pub top_left: f32,
pub top_right: f32,
pub bottom_right: f32,
pub bottom_left: f32,
}
impl ResolvedBorderRadius {
pub const ZERO: Self = Self {
top_left: 0.,
top_right: 0.,
bottom_right: 0.,
bottom_left: 0.,
};
}
impl From<ResolvedBorderRadius> for [f32; 4] {
fn from(radius: ResolvedBorderRadius) -> Self {
[
radius.top_left,
radius.top_right,
radius.bottom_right,
radius.bottom_left,
]
}
}
#[derive(Component, Clone, Debug, Default, PartialEq, Reflect, Deref, DerefMut)]
#[reflect(Component, PartialEq, Default, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct BoxShadow(pub Vec<ShadowStyle>);
impl BoxShadow {
pub fn new(
color: Color,
x_offset: Val,
y_offset: Val,
spread_radius: Val,
blur_radius: Val,
) -> Self {
Self(vec![ShadowStyle {
color,
x_offset,
y_offset,
spread_radius,
blur_radius,
}])
}
}
impl From<ShadowStyle> for BoxShadow {
fn from(value: ShadowStyle) -> Self {
Self(vec![value])
}
}
#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
#[reflect(PartialEq, Default, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct ShadowStyle {
pub color: Color,
pub x_offset: Val,
pub y_offset: Val,
pub spread_radius: Val,
pub blur_radius: Val,
}
impl Default for ShadowStyle {
fn default() -> Self {
Self {
color: Color::BLACK,
x_offset: Val::Percent(20.),
y_offset: Val::Percent(20.),
spread_radius: Val::ZERO,
blur_radius: Val::Percent(10.),
}
}
}
#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
#[reflect(Component, Debug, PartialEq, Default, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct LayoutConfig {
pub use_rounding: bool,
}
impl Default for LayoutConfig {
fn default() -> Self {
Self { use_rounding: true }
}
}
#[derive(Component, Clone, Debug, Reflect, Eq, PartialEq)]
#[reflect(Component, Debug, PartialEq, Clone)]
pub struct UiTargetCamera(pub Entity);
impl UiTargetCamera {
pub fn entity(&self) -> Entity {
self.0
}
}
#[derive(Component, Default)]
pub struct IsDefaultUiCamera;
#[derive(SystemParam)]
pub struct DefaultUiCamera<'w, 's> {
cameras: Query<'w, 's, (Entity, &'static Camera, &'static RenderTarget)>,
default_cameras: Query<'w, 's, Entity, (With<Camera>, With<IsDefaultUiCamera>)>,
primary_window: Query<'w, 's, Entity, With<PrimaryWindow>>,
}
impl<'w, 's> DefaultUiCamera<'w, 's> {
pub fn get(&self) -> Option<Entity> {
self.default_cameras.single().ok().or_else(|| {
if !self.default_cameras.is_empty() {
once!(warn!("Two or more Entities with IsDefaultUiCamera found when only one Camera with this marker is allowed."));
}
self.cameras
.iter()
.filter(|(_, _, render_target)| match render_target {
RenderTarget::Window(WindowRef::Primary) => true,
RenderTarget::Window(WindowRef::Entity(w)) => {
self.primary_window.get(*w).is_ok()
}
_ => false,
})
.max_by_key(|(e, c, _)| (c.order, *e))
.map(|(e, _, _)| e)
})
}
}
#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)]
#[reflect(Component, Default, PartialEq, Clone)]
pub struct ComputedUiTargetCamera {
pub(crate) camera: Entity,
}
impl Default for ComputedUiTargetCamera {
fn default() -> Self {
Self {
camera: Entity::PLACEHOLDER,
}
}
}
impl ComputedUiTargetCamera {
pub fn get(&self) -> Option<Entity> {
Some(self.camera).filter(|&entity| entity != Entity::PLACEHOLDER)
}
}
#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)]
#[reflect(Component, Default, PartialEq, Clone)]
pub struct ComputedUiRenderTargetInfo {
pub(crate) scale_factor: f32,
pub(crate) physical_size: UVec2,
}
impl Default for ComputedUiRenderTargetInfo {
fn default() -> Self {
Self {
scale_factor: 1.,
physical_size: UVec2::ZERO,
}
}
}
impl ComputedUiRenderTargetInfo {
pub const fn scale_factor(&self) -> f32 {
self.scale_factor
}
pub const fn physical_size(&self) -> UVec2 {
self.physical_size
}
pub fn logical_size(&self) -> Vec2 {
self.physical_size.as_vec2() / self.scale_factor
}
}
#[cfg(test)]
mod tests {
use crate::ComputedNode;
use crate::GridPlacement;
use bevy_math::{Rect, Vec2};
use bevy_sprite::BorderRect;
#[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);
}
#[test]
fn computed_node_both_scrollbars() {
let node = ComputedNode {
size: Vec2::splat(100.),
scrollbar_size: Vec2::splat(10.),
content_size: Vec2::splat(100.),
..Default::default()
};
let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(-50., 40.),
max: Vec2::new(40., 50.)
}
);
assert_eq!(thumb, [-50., 31.]);
let (gutter, thumb) = node.vertical_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(40., -50.),
max: Vec2::new(50., 40.)
}
);
assert_eq!(thumb, [-50., 31.]);
}
#[test]
fn computed_node_single_horizontal_scrollbar() {
let mut node = ComputedNode {
size: Vec2::splat(100.),
scrollbar_size: Vec2::new(0., 10.),
content_size: Vec2::new(200., 100.),
scroll_position: Vec2::new(0., 0.),
..Default::default()
};
assert_eq!(None, node.vertical_scrollbar());
let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(-50., 40.),
max: Vec2::new(50., 50.)
}
);
assert_eq!(thumb, [-50., 0.]);
node.scroll_position.x += 100.;
let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(-50., 40.),
max: Vec2::new(50., 50.)
}
);
assert_eq!(thumb, [0., 50.]);
}
#[test]
fn computed_node_single_vertical_scrollbar() {
let mut node = ComputedNode {
size: Vec2::splat(100.),
scrollbar_size: Vec2::new(10., 0.),
content_size: Vec2::new(100., 200.),
scroll_position: Vec2::new(0., 0.),
..Default::default()
};
assert_eq!(None, node.horizontal_scrollbar());
let (gutter, thumb) = node.vertical_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(40., -50.),
max: Vec2::new(50., 50.)
}
);
assert_eq!(thumb, [-50., 0.]);
node.scroll_position.y += 100.;
let (gutter, thumb) = node.vertical_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(40., -50.),
max: Vec2::new(50., 50.)
}
);
assert_eq!(thumb, [0., 50.]);
}
#[test]
fn border_box_is_centered_rect_of_node_size() {
let node = ComputedNode {
size: Vec2::new(100.0, 50.0),
..Default::default()
};
let border_box = node.border_box();
assert_eq!(border_box.min, Vec2::new(-50.0, -25.0));
assert_eq!(border_box.max, Vec2::new(50.0, 25.0));
}
#[test]
fn padding_box_subtracts_border_thickness() {
let node = ComputedNode {
size: Vec2::new(100.0, 60.0),
border: BorderRect {
min_inset: Vec2::new(5.0, 3.0),
max_inset: Vec2::new(7.0, 9.0),
},
..Default::default()
};
let padding_box = node.padding_box();
assert_eq!(padding_box.min, Vec2::new(-50.0 + 5.0, -30.0 + 3.0));
assert_eq!(padding_box.max, Vec2::new(50.0 - 7.0, 30.0 - 9.0));
}
#[test]
fn content_box_uses_content_inset() {
let node = ComputedNode {
size: Vec2::new(80.0, 40.0),
padding: BorderRect {
min_inset: Vec2::new(4.0, 2.0),
max_inset: Vec2::new(6.0, 8.0),
},
..Default::default()
};
let content_box = node.content_box();
assert_eq!(content_box.min, Vec2::new(-40.0 + 4.0, -20.0 + 2.0));
assert_eq!(content_box.max, Vec2::new(40.0 - 6.0, 20.0 - 8.0));
}
}