use std::ops::Deref;
use ori_macro::{example, Build};
use crate::{
context::{BuildCx, DrawCx, EventCx, LayoutCx, RebuildCx},
event::Event,
layout::{Align, Axis, Justify, Size, Space},
rebuild::Rebuild,
style::{Stylable, Style, StyleBuilder},
view::{AnyView, PodSeq, SeqState, View, ViewSeq},
};
pub use crate::{hstack, vstack};
use super::Flex;
#[macro_export]
macro_rules! hstack {
($($child:expr),* $(,)?) => {
$crate::views::hstack(($($child,)*))
};
}
#[macro_export]
macro_rules! vstack {
($($child:expr),* $(,)?) => {
$crate::views::vstack(($($child,)*))
};
}
pub fn hstack<V>(view: V) -> Stack<V> {
Stack::horizontal(view)
}
pub fn vstack<V>(view: V) -> Stack<V> {
Stack::vertical(view)
}
pub fn hstack_vec<V>() -> Stack<Vec<V>> {
Stack::vec_horizontal()
}
pub fn vstack_vec<V>() -> Stack<Vec<V>> {
Stack::vec_vertical()
}
pub fn hstack_any<'a, T>() -> Stack<Vec<Box<dyn AnyView<T> + 'a>>> {
Stack::any_horizontal()
}
pub fn vstack_any<'a, T>() -> Stack<Vec<Box<dyn AnyView<T> + 'a>>> {
Stack::any_vertical()
}
#[derive(Clone, Rebuild)]
pub struct StackStyle {
#[rebuild(layout)]
pub justify: Justify,
#[rebuild(layout)]
pub align: Align,
#[rebuild(layout)]
pub gap: f32,
}
impl Style for StackStyle {
fn default_style() -> StyleBuilder<Self> {
StyleBuilder::new(|| Self {
justify: Justify::Start,
align: Align::Center,
gap: 0.0,
})
}
}
#[example(name = "stack", width = 400, height = 600)]
#[derive(Build, Rebuild)]
pub struct Stack<V> {
#[build(ignore)]
pub content: PodSeq<V>,
#[rebuild(layout)]
pub axis: Axis,
#[rebuild(layout)]
#[style(default)]
pub justify: Option<Justify>,
#[rebuild(layout)]
#[style(default = Align::Center)]
pub align: Option<Align>,
#[rebuild(layout)]
#[style(default)]
pub gap: Option<f32>,
}
impl<V> Stack<V> {
pub fn new(axis: Axis, content: V) -> Self {
Self {
content: PodSeq::new(content),
axis,
justify: None,
align: None,
gap: None,
}
}
pub fn horizontal(content: V) -> Self {
Self::new(Axis::Horizontal, content)
}
pub fn vertical(content: V) -> Self {
Self::new(Axis::Vertical, content)
}
}
impl<T> Stack<Vec<T>> {
pub fn vec(axis: Axis) -> Self {
Self::new(axis, Vec::new())
}
pub fn vec_horizontal() -> Self {
Self::horizontal(Vec::new())
}
pub fn vec_vertical() -> Self {
Self::vertical(Vec::new())
}
pub fn push(&mut self, view: T) {
self.content.push(view);
}
pub fn with(mut self, view: T) -> Self {
self.push(view);
self
}
pub fn is_empty(&self) -> bool {
self.content.deref().is_empty()
}
pub fn len(&self) -> usize {
self.content.deref().len()
}
}
impl<T> Stack<Vec<Box<dyn AnyView<T> + '_>>> {
pub fn any(axis: Axis) -> Self {
Self::new(axis, Vec::new())
}
pub fn any_horizontal() -> Self {
Self::horizontal(Vec::new())
}
pub fn any_vertical() -> Self {
Self::vertical(Vec::new())
}
}
impl<V> Stylable for Stack<V> {
type Style = StackStyle;
fn style(&self, style: &Self::Style) -> Self::Style {
StackStyle {
justify: self.justify.unwrap_or(style.justify),
align: self.align.unwrap_or(style.align),
gap: self.gap.unwrap_or(style.gap),
}
}
}
#[doc(hidden)]
pub struct StackState {
style: StackStyle,
flex_sum: f32,
majors: Vec<f32>,
minors: Vec<f32>,
}
impl StackState {
fn new<T, V: ViewSeq<T>>(stack: &Stack<V>, style: &StackStyle) -> Self {
Self {
style: stack.style(style),
flex_sum: 0.0,
majors: vec![0.0; stack.content.len()],
minors: vec![0.0; stack.content.len()],
}
}
fn resize(&mut self, len: usize) {
self.majors.resize(len, 0.0);
self.minors.resize(len, 0.0);
}
fn major(&self) -> f32 {
self.majors.iter().copied().sum()
}
fn minor(&self) -> f32 {
let mut total = 0.0;
for minor in self.minors.iter().copied() {
total = f32::max(total, minor);
}
total
}
}
impl<T, V: ViewSeq<T>> View<T> for Stack<V> {
type State = (StackState, SeqState<T, V>);
fn build(&mut self, cx: &mut BuildCx, data: &mut T) -> Self::State {
(
StackState::new(self, cx.style()),
self.content.build(cx, data),
)
}
fn rebuild(
&mut self,
(state, content): &mut Self::State,
cx: &mut RebuildCx,
data: &mut T,
old: &Self,
) {
Rebuild::rebuild(self, cx, old);
self.rebuild_style(cx, &mut state.style);
if self.content.len() != old.content.len() {
state.resize(self.content.len());
cx.layout();
}
(self.content).rebuild(content, &mut cx.as_build_cx(), data, &old.content);
for i in 0..self.content.len() {
self.content.rebuild_nth(i, content, cx, data, &old.content)
}
}
fn event(
&mut self,
(_, content): &mut Self::State,
cx: &mut EventCx,
data: &mut T,
event: &Event,
) -> bool {
self.content.event(content, cx, data, event)
}
fn layout(
&mut self,
(state, content): &mut Self::State,
cx: &mut LayoutCx,
data: &mut T,
space: Space,
) -> Size {
let (min_major, min_minor) = self.axis.unpack(space.min);
let (max_major, max_minor) = self.axis.unpack(space.max);
let min_major = min_major.min(max_major);
let min_minor = min_minor.min(max_minor);
let total_gap = state.style.gap * (self.content.len() as f32 - 1.0);
let stretch_full = state.style.align == Align::Stretch && min_minor == max_minor;
if state.style.align == Align::Fill || stretch_full {
layout(
self, cx, content, state, data, max_major, max_minor, max_minor, total_gap,
);
} else {
layout(
self, cx, content, state, data, max_major, 0.0, max_minor, total_gap,
);
if state.style.align == Align::Stretch {
let minor = f32::clamp(state.minor(), min_minor, max_minor);
layout(
self, cx, content, state, data, max_major, minor, minor, total_gap,
);
}
}
let major = f32::clamp(state.major() + total_gap, min_major, max_major);
let minor = f32::clamp(state.minor(), min_minor, max_minor);
for (i, child_major) in (state.style.justify)
.layout(&state.majors, major, state.style.gap)
.enumerate()
{
let child_align = state.style.align.align(minor, state.minors[i]);
let offset = self.axis.pack(child_major, child_align);
content[i].translate(offset);
}
self.axis.pack(major, minor)
}
fn draw(&mut self, (_, content): &mut Self::State, cx: &mut DrawCx, data: &mut T) {
for i in 0..self.content.len() {
self.content.draw_nth(i, content, cx, data);
}
}
}
#[allow(clippy::too_many_arguments)]
fn layout<T, V: ViewSeq<T>>(
stack: &mut Stack<V>,
cx: &mut LayoutCx,
content: &mut SeqState<T, V>,
state: &mut StackState,
data: &mut T,
max_major: f32,
min_minor: f32,
max_minor: f32,
total_gap: f32,
) {
state.flex_sum = 0.0;
for i in 0..stack.content.len() {
if let Some(flex) = content[i].get_property::<Flex>() {
state.flex_sum += flex.amount;
state.majors[i] = 0.0;
continue;
}
let space = Space::new(
stack.axis.pack(0.0, min_minor),
stack.axis.pack(f32::INFINITY, max_minor),
);
let size = stack.content.layout_nth(i, content, cx, data, space);
state.majors[i] = stack.axis.major(size);
state.minors[i] = stack.axis.minor(size);
}
let remaining = f32::max(max_major - total_gap - state.major(), 0.0);
let per_flex = remaining / state.flex_sum;
for i in 0..stack.content.len() {
let Some(flex) = content[i].get_property::<Flex>() else {
continue;
};
if !flex.is_tight {
continue;
}
let major = per_flex * flex.amount;
let space = Space::new(
stack.axis.pack(0.0, min_minor),
stack.axis.pack(major, max_minor),
);
let size = stack.content.layout_nth(i, content, cx, data, space);
state.majors[i] = stack.axis.major(size);
state.minors[i] = stack.axis.minor(size);
}
let remaining = f32::max(max_major - total_gap - state.major(), 0.0);
let per_flex = remaining / state.flex_sum;
for i in 0..stack.content.len() {
let Some(flex) = content[i].get_property::<Flex>() else {
continue;
};
if flex.is_tight {
continue;
}
let major = per_flex * flex.amount;
let space = Space::new(
stack.axis.pack(major, min_minor),
stack.axis.pack(major, max_minor),
);
let size = stack.content.layout_nth(i, content, cx, data, space);
state.majors[i] = stack.axis.major(size);
state.minors[i] = stack.axis.minor(size);
}
}