use crate::geometry::{AbsoluteAxis, Line, Point, Rect, Size};
use crate::style::{AlignItemsKeyword, AlignSelf, AlignmentSafety, AvailableSpace, CheapCloneStr, Position};
use crate::style_helpers::TaffyMaxContent;
use crate::sys::DefaultCheapStr;
use crate::tree::NodeId;
use crate::util::sys::{f32_max, f32_min};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum RunMode {
PerformLayout,
ComputeSize,
PerformHiddenLayout,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum SizingMode {
ContentSize,
InherentSize,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct CollapsibleMarginSet {
positive: f32,
negative: f32,
}
impl CollapsibleMarginSet {
pub const ZERO: Self = Self { positive: 0.0, negative: 0.0 };
pub fn from_margin(margin: f32) -> Self {
if margin >= 0.0 {
Self { positive: margin, negative: 0.0 }
} else {
Self { positive: 0.0, negative: margin }
}
}
pub fn collapse_with_margin(mut self, margin: f32) -> Self {
if margin >= 0.0 {
self.positive = f32_max(self.positive, margin);
} else {
self.negative = f32_min(self.negative, margin);
}
self
}
pub fn collapse_with_set(mut self, other: CollapsibleMarginSet) -> Self {
self.positive = f32_max(self.positive, other.positive);
self.negative = f32_min(self.negative, other.negative);
self
}
pub fn resolve(&self) -> f32 {
self.positive + self.negative
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum RequestedAxis {
Horizontal,
Vertical,
Both,
}
impl RequestedAxis {
#[inline(always)]
pub fn transpose(self) -> RequestedAxis {
match self {
RequestedAxis::Horizontal => RequestedAxis::Vertical,
RequestedAxis::Vertical => RequestedAxis::Horizontal,
RequestedAxis::Both => RequestedAxis::Both,
}
}
}
impl From<AbsoluteAxis> for RequestedAxis {
fn from(value: AbsoluteAxis) -> Self {
match value {
AbsoluteAxis::Horizontal => RequestedAxis::Horizontal,
AbsoluteAxis::Vertical => RequestedAxis::Vertical,
}
}
}
impl TryFrom<RequestedAxis> for AbsoluteAxis {
type Error = ();
fn try_from(value: RequestedAxis) -> Result<Self, Self::Error> {
match value {
RequestedAxis::Horizontal => Ok(AbsoluteAxis::Horizontal),
RequestedAxis::Vertical => Ok(AbsoluteAxis::Vertical),
RequestedAxis::Both => Err(()),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct LayoutInput {
pub run_mode: RunMode,
pub sizing_mode: SizingMode,
pub axis: RequestedAxis,
pub known_dimensions: Size<Option<f32>>,
pub known_dimensions_are_definite: Size<bool>,
pub parent_size: Size<Option<f32>>,
pub available_space: Size<AvailableSpace>,
pub vertical_margins_are_collapsible: Line<bool>,
}
impl LayoutInput {
pub const HIDDEN: LayoutInput = LayoutInput {
run_mode: RunMode::PerformHiddenLayout,
known_dimensions: Size::NONE,
known_dimensions_are_definite: Size { width: true, height: true },
parent_size: Size::NONE,
available_space: Size::MAX_CONTENT,
sizing_mode: SizingMode::InherentSize,
axis: RequestedAxis::Both,
vertical_margins_are_collapsible: Line::FALSE,
};
pub fn transpose(self) -> LayoutInput {
LayoutInput {
run_mode: self.run_mode,
sizing_mode: self.sizing_mode,
axis: self.axis.transpose(),
known_dimensions: self.known_dimensions.transpose(),
known_dimensions_are_definite: self.known_dimensions_are_definite.transpose(),
parent_size: self.parent_size.transpose(),
available_space: self.available_space.transpose(),
vertical_margins_are_collapsible: Line::FALSE,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Baselines {
pub first: Option<f32>,
pub last: Option<f32>,
}
impl Baselines {
pub const NONE: Self = Self { first: None, last: None };
pub const fn from_first(first: Option<f32>) -> Self {
Self { first, last: None }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AxisStaticEdge {
Start,
Center,
End,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AxisStaticAlign {
pub keyword: AxisStaticEdge,
pub safety: AlignmentSafety,
pub fallback: AxisStaticEdge,
}
impl AxisStaticAlign {
pub const fn from_keyword(keyword: AxisStaticEdge) -> Self {
Self { keyword, safety: AlignmentSafety::Default, fallback: keyword }
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct AxisStaticPosition {
pub area: Line<f32>,
pub align: AxisStaticAlign,
}
impl AxisStaticPosition {
pub const fn from_edge(anchor: f32, edge: AxisStaticEdge) -> Self {
Self { area: Line { start: anchor, end: anchor }, align: AxisStaticAlign::from_keyword(edge) }
}
pub fn from_alignment(alignment: AlignSelf, area: Line<f32>, axis_is_rtl: bool) -> Self {
debug_assert!(
!matches!(alignment.keyword, AlignItemsKeyword::SelfStart | AlignItemsKeyword::SelfEnd),
"self-start/self-end must be resolved before computing the static position"
);
fn edge_for(keyword: AlignItemsKeyword, axis_is_rtl: bool) -> AxisStaticEdge {
let start_position = !matches!(keyword, AlignItemsKeyword::End | AlignItemsKeyword::FlexEnd) ^ axis_is_rtl;
match keyword {
AlignItemsKeyword::Center => AxisStaticEdge::Center,
_ if start_position => AxisStaticEdge::Start,
_ => AxisStaticEdge::End,
}
}
let fallback = if matches!(alignment.safety, AlignmentSafety::Safe) {
AlignItemsKeyword::Start
} else {
alignment.keyword
};
Self {
area,
align: AxisStaticAlign {
keyword: edge_for(alignment.keyword, axis_is_rtl),
safety: alignment.safety,
fallback: edge_for(fallback, axis_is_rtl),
},
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct OofCandidate {
pub node: NodeId,
pub order: u32,
pub position: Position,
pub static_position: Point<AxisStaticPosition>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct OofCandidates {
inner: Option<crate::util::sys::Box<crate::util::sys::Vec<OofCandidate>>>,
}
impl OofCandidates {
pub const NONE: Self = Self { inner: None };
pub const fn new() -> Self {
Self::NONE
}
#[inline]
pub fn is_empty(&self) -> bool {
self.as_slice().is_empty()
}
#[inline]
pub fn as_slice(&self) -> &[OofCandidate] {
self.inner.as_ref().map(|boxed| boxed.as_slice()).unwrap_or(&[])
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [OofCandidate] {
self.inner.as_mut().map(|boxed| boxed.as_mut_slice()).unwrap_or(&mut [])
}
#[inline]
pub fn push(&mut self, candidate: OofCandidate) {
self.inner.get_or_insert_with(Default::default).push(candidate);
}
#[inline]
pub fn append(&mut self, other: &mut OofCandidates) {
if other.is_empty() {
return;
}
match &mut self.inner {
None => self.inner = other.inner.take(),
Some(vec) => vec.append(other.inner.as_mut().unwrap()),
}
}
#[inline]
pub fn take(&mut self) -> Self {
core::mem::take(self)
}
#[inline]
pub fn clear(&mut self) {
self.inner = None;
}
#[inline]
pub fn iter(&self) -> core::slice::Iter<'_, OofCandidate> {
self.as_slice().iter()
}
#[inline]
pub fn translate(&mut self, offset: Point<f32>) {
for candidate in self.as_mut_slice() {
candidate.static_position.x.area.start += offset.x;
candidate.static_position.x.area.end += offset.x;
candidate.static_position.y.area.start += offset.y;
candidate.static_position.y.area.end += offset.y;
}
}
}
impl<'a> IntoIterator for &'a OofCandidates {
type Item = &'a OofCandidate;
type IntoIter = core::slice::Iter<'a, OofCandidate>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct OofPositioningArea {
pub size: Size<f32>,
pub offset: Point<f32>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct LayoutOutput {
pub size: Size<f32>,
#[cfg(feature = "content_size")]
pub scrollable_overflow_rect: Rect<f32>,
pub baselines: Baselines,
pub top_margin: CollapsibleMarginSet,
pub bottom_margin: CollapsibleMarginSet,
pub margins_can_collapse_through: bool,
#[cfg_attr(feature = "serde", serde(skip_serializing))]
pub oof_candidates: OofCandidates,
pub oof_positioning_area: Option<OofPositioningArea>,
}
impl LayoutOutput {
pub const HIDDEN: Self = Self {
size: Size::ZERO,
#[cfg(feature = "content_size")]
scrollable_overflow_rect: Rect::ZERO,
baselines: Baselines::NONE,
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: false,
oof_candidates: OofCandidates::NONE,
oof_positioning_area: None,
};
pub const DEFAULT: Self = Self::HIDDEN;
pub fn from_sizes_and_baselines(
size: Size<f32>,
#[cfg_attr(not(feature = "content_size"), allow(unused_variables))] scrollable_overflow_rect: Rect<f32>,
baselines: Baselines,
) -> Self {
Self {
size,
#[cfg(feature = "content_size")]
scrollable_overflow_rect,
baselines,
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: false,
oof_candidates: OofCandidates::NONE,
oof_positioning_area: None,
}
}
pub fn from_sizes(size: Size<f32>, scrollable_overflow_rect: Rect<f32>) -> Self {
Self::from_sizes_and_baselines(size, scrollable_overflow_rect, Baselines::NONE)
}
pub fn transpose(self) -> Self {
Self {
size: self.size.transpose(),
#[cfg(feature = "content_size")]
scrollable_overflow_rect: self.scrollable_overflow_rect.transpose(),
baselines: Baselines::NONE,
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: false,
oof_candidates: self.oof_candidates,
oof_positioning_area: self
.oof_positioning_area
.map(|area| OofPositioningArea { size: area.size.transpose(), offset: area.offset.transpose() }),
}
}
pub fn from_outer_size(size: Size<f32>) -> Self {
Self::from_sizes(size, Rect::ZERO)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Layout {
pub order: u32,
pub location: Point<f32>,
pub size: Size<f32>,
#[cfg(feature = "content_size")]
pub scrollable_overflow_rect: Rect<f32>,
pub scrollbar_size: Size<f32>,
pub border: Rect<f32>,
pub padding: Rect<f32>,
pub margin: Rect<f32>,
}
impl Default for Layout {
fn default() -> Self {
Self::new()
}
}
impl Layout {
#[must_use]
pub const fn new() -> Self {
Self {
order: 0,
location: Point::ZERO,
size: Size::zero(),
#[cfg(feature = "content_size")]
scrollable_overflow_rect: Rect::ZERO,
scrollbar_size: Size::zero(),
border: Rect::zero(),
padding: Rect::zero(),
margin: Rect::zero(),
}
}
#[must_use]
pub const fn with_order(order: u32) -> Self {
Self {
order,
size: Size::zero(),
location: Point::ZERO,
#[cfg(feature = "content_size")]
scrollable_overflow_rect: Rect::ZERO,
scrollbar_size: Size::zero(),
border: Rect::zero(),
padding: Rect::zero(),
margin: Rect::zero(),
}
}
#[inline]
pub fn content_box_width(&self) -> f32 {
self.size.width - self.padding.left - self.padding.right - self.border.left - self.border.right
}
#[inline]
pub fn content_box_height(&self) -> f32 {
self.size.height - self.padding.top - self.padding.bottom - self.border.top - self.border.bottom
}
#[inline]
pub fn content_box_size(&self) -> Size<f32> {
Size { width: self.content_box_width(), height: self.content_box_height() }
}
pub fn content_box_x(&self) -> f32 {
self.location.x + self.border.left + self.padding.left
}
pub fn content_box_y(&self) -> f32 {
self.location.y + self.border.top + self.padding.top
}
}
#[cfg(feature = "content_size")]
impl Layout {
pub fn scroll_width(&self) -> f32 {
f32_max(
0.0,
self.scrollable_overflow_rect.right + f32_min(self.scrollbar_size.width, self.size.width) - self.size.width
+ self.border.left
+ self.border.right,
)
}
pub fn scroll_height(&self) -> f32 {
f32_max(
0.0,
self.scrollable_overflow_rect.bottom + f32_min(self.scrollbar_size.height, self.size.height)
- self.size.height
+ self.border.top
+ self.border.bottom,
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum DetailedLayoutInfo<S: CheapCloneStr = DefaultCheapStr> {
#[cfg(feature = "grid")]
Grid(crate::util::sys::Box<crate::compute::grid::DetailedGridInfo<S>>),
None,
#[cfg(not(feature = "grid"))]
#[doc(hidden)]
Phantom(core::convert::Infallible, core::marker::PhantomData<S>),
}