use std::rc::Rc;
use day_core::*;
use day_reactive::{AnyBinding, Binding, watch};
use day_spec::kinds;
use day_spec::props::*;
use crate::Decorated;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum HAlign {
Leading,
#[default]
Center,
Trailing,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum VAlign {
Top,
#[default]
Center,
Bottom,
FirstBaseline,
}
pub struct Column<C: PieceSeq> {
children: C,
spacing: f64,
align: CrossAlign,
}
pub fn column<C: PieceSeq>(children: C) -> Column<C> {
Column {
children,
spacing: 0.0,
align: CrossAlign::Center,
}
}
#[day_macros::builder(ColumnBuilder)]
impl<C: PieceSeq> Column<C> {
pub fn spacing(mut self, s: impl Into<f64>) -> Self {
self.spacing = s.into();
self
}
pub fn align(mut self, a: HAlign) -> Self {
self.align = match a {
HAlign::Leading => CrossAlign::Leading,
HAlign::Center => CrossAlign::Center,
HAlign::Trailing => CrossAlign::Trailing,
};
self
}
}
impl<C: PieceSeq> Piece for Column<C> {
fn build(self, cx: &mut BuildCx) -> RNode {
let node = cx.native(
kinds::CONTAINER,
&ContainerProps::default(),
Rc::new(StackLayout {
axis: Axis::Vertical,
spacing: self.spacing,
align: self.align,
}),
Flex::default(),
Boundary::No,
);
cx.under(node, |cx| self.children.build_each(cx));
node
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum RowFit {
#[default]
Clip,
Wrap { run_spacing: f64 },
WrapColumns { run_spacing: f64 },
Scroll,
}
pub struct Row<C: PieceSeq> {
children: C,
spacing: f64,
align: CrossAlign,
fit: RowFit,
}
pub fn row<C: PieceSeq>(children: C) -> Row<C> {
Row {
children,
spacing: 0.0,
align: CrossAlign::Center,
fit: RowFit::Clip,
}
}
#[day_macros::builder(RowBuilder)]
impl<C: PieceSeq> Row<C> {
pub fn spacing(mut self, s: impl Into<f64>) -> Self {
self.spacing = s.into();
self
}
pub fn align(mut self, a: VAlign) -> Self {
self.align = match a {
VAlign::Top => CrossAlign::Leading,
VAlign::Center => CrossAlign::Center,
VAlign::Bottom => CrossAlign::Trailing,
VAlign::FirstBaseline => CrossAlign::FirstBaseline,
};
self
}
pub fn fit(mut self, fit: RowFit) -> Self {
self.fit = fit;
self
}
}
impl<C: PieceSeq> Row<C> {
fn build_line(self, cx: &mut BuildCx, axis: Axis, align: CrossAlign) -> RNode {
let node = cx.native(
kinds::CONTAINER,
&ContainerProps::default(),
Rc::new(StackLayout {
axis,
spacing: self.spacing,
align,
}),
Flex::default(),
Boundary::No,
);
cx.under(node, |cx| self.children.build_each(cx));
node
}
}
impl<C: PieceSeq> Piece for Row<C> {
fn build(self, cx: &mut BuildCx) -> RNode {
match self.fit {
RowFit::Clip => {
let align = self.align;
self.build_line(cx, Axis::Horizontal, align)
}
RowFit::Wrap { run_spacing } | RowFit::WrapColumns { run_spacing } => {
let node = cx.native(
kinds::CONTAINER,
&ContainerProps::default(),
Rc::new(FlowLayout {
spacing: self.spacing,
run_spacing,
align: self.align,
uniform: matches!(self.fit, RowFit::WrapColumns { .. }),
}),
Flex::default(),
Boundary::No,
);
cx.under(node, |cx| self.children.build_each(cx));
node
}
RowFit::Scroll => {
let strip = cx.native(
kinds::SCROLL,
&day_spec::props::ScrollProps { horizontal: true },
Rc::new(ScrollLayout {
axis: Axis::Horizontal,
}),
Flex {
grow_w: true,
..Default::default()
},
Boundary::Yes, );
cx.under(strip, |cx| {
let align = self.align;
let _ = self.build_line(cx, Axis::Horizontal, align);
});
strip
}
}
}
}
pub struct Grid<C: PieceSeq> {
children: C,
row_spacing: f64,
column_spacing: f64,
align: Alignment,
}
pub fn grid<C: PieceSeq>(children: C) -> Grid<C> {
Grid {
children,
row_spacing: 0.0,
column_spacing: 0.0,
align: Alignment::Center,
}
}
#[day_macros::builder(GridBuilder)]
impl<C: PieceSeq> Grid<C> {
pub fn spacing(mut self, s: impl Into<f64>) -> Self {
let s = s.into();
self.row_spacing = s;
self.column_spacing = s;
self
}
pub fn row_spacing(mut self, s: impl Into<f64>) -> Self {
self.row_spacing = s.into();
self
}
pub fn column_spacing(mut self, s: impl Into<f64>) -> Self {
self.column_spacing = s.into();
self
}
pub fn align(mut self, a: Alignment) -> Self {
self.align = a;
self
}
}
impl<C: PieceSeq> Piece for Grid<C> {
fn build(self, cx: &mut BuildCx) -> RNode {
let node = cx.native(
kinds::CONTAINER,
&ContainerProps::default(),
Rc::new(GridLayout {
row_spacing: self.row_spacing,
column_spacing: self.column_spacing,
align: self.align,
}),
Flex::default(),
Boundary::No,
);
cx.under(node, |cx| self.children.build_each(cx));
node
}
}
pub struct GridRow<C: PieceSeq> {
children: C,
valign: Option<CrossAlign>,
}
pub fn grid_row<C: PieceSeq>(children: C) -> GridRow<C> {
GridRow {
children,
valign: None,
}
}
#[day_macros::builder(GridRowBuilder)]
impl<C: PieceSeq> GridRow<C> {
pub fn align(mut self, a: VAlign) -> Self {
self.valign = Some(match a {
VAlign::Top => CrossAlign::Leading,
VAlign::Center => CrossAlign::Center,
VAlign::Bottom => CrossAlign::Trailing,
VAlign::FirstBaseline => CrossAlign::FirstBaseline,
});
self
}
}
impl<C: PieceSeq> Piece for GridRow<C> {
fn build(self, cx: &mut BuildCx) -> RNode {
let node = cx.layout_only(
Rc::new(StackLayout {
axis: Axis::Horizontal,
spacing: 0.0,
align: self.valign.unwrap_or_default(),
}),
Flex {
grid: GridFacts {
is_row: true,
row_valign: self.valign,
..Default::default()
},
..Default::default()
},
Boundary::No,
);
cx.under(node, |cx| self.children.build_each(cx));
node
}
}
pub struct Scroll<P: Piece> {
child: P,
axis: Axis,
target: Option<AnyBinding<Option<day_core::ScrollTarget>>>,
}
pub fn scroll<P: Piece>(child: P) -> Scroll<P> {
Scroll {
child,
axis: Axis::Vertical,
target: None,
}
}
#[day_macros::builder(ScrollBuilder)]
impl<P: Piece> Scroll<P> {
pub fn horizontal(mut self) -> Self {
self.axis = Axis::Horizontal;
self
}
pub fn axis(mut self, axis: Axis) -> Self {
self.axis = axis;
self
}
pub fn scroll_target(mut self, target: impl Binding<Option<day_core::ScrollTarget>>) -> Self {
self.target = Some(AnyBinding::new(target));
self
}
}
impl<P: Piece> Piece for Scroll<P> {
fn build(self, cx: &mut BuildCx) -> RNode {
let node = cx.native(
kinds::SCROLL,
&day_spec::props::ScrollProps {
horizontal: matches!(self.axis, Axis::Horizontal),
},
Rc::new(ScrollLayout { axis: self.axis }),
Flex {
grow_w: true,
grow_h: true,
..Default::default()
},
Boundary::Yes, );
cx.under(node, |cx| {
let _ = self.child.build(cx);
});
if let Some(sig) = self.target {
watch(
move || sig.get(),
move |now, _| {
if let Some(t) = now.clone() {
day_reactive::on_main(move || {
day_core::with_tree(|tr| {
tr.scroll_to_target(node, &t, true);
});
});
sig.set(None); }
},
);
}
node
}
}
pub struct ZStack<C: PieceSeq> {
children: C,
align: Alignment,
}
pub fn zstack<C: PieceSeq>(children: C) -> ZStack<C> {
ZStack {
children,
align: Alignment::Center,
}
}
#[day_macros::builder(ZStackBuilder)]
impl<C: PieceSeq> ZStack<C> {
pub fn align(mut self, a: Alignment) -> Self {
self.align = a;
self
}
}
impl<C: PieceSeq> Piece for ZStack<C> {
fn build(self, cx: &mut BuildCx) -> RNode {
let node = cx.native(
kinds::CONTAINER,
&ContainerProps::default(),
Rc::new(OverlayLayout {
align: self.align,
size_to_first: false,
}),
Flex::default(),
Boundary::No,
);
cx.under(node, |cx| self.children.build_each(cx));
node
}
}
#[cfg(feature = "conformance")]
pub(crate) mod conformance {
use day_core::conformance::{Case, Drive, FrameExpect};
use day_core::{AnyPiece, ScrollTarget};
use day_reactive::Signal;
use day_spec::{Color, kinds};
use crate::*;
fn block(color: Color, id: day_core::ElementId, w: f64, h: f64) -> AnyPiece {
rectangle().fill(color).id(id).frame(w, h).any()
}
fn at(relative_to: &str, x: Option<f64>, y: Option<f64>) -> FrameExpect {
FrameExpect {
x,
y,
relative_to: Some(relative_to.into()),
..Default::default()
}
}
#[day_macros::test(day_core)]
fn column_spacing() -> Case {
Case::new()
.proves_duty("realize")
.proves_duty("insert")
.proves_duty("set_frame")
.proves_duty("measure")
.proves(kinds::CONTAINER)
.page(|| {
column((
block(Color::rgb(1.0, 0.0, 0.0), day_core::id!(a), 50.0, 20.0),
block(Color::rgb(0.0, 0.0, 1.0), day_core::id!(b), 50.0, 20.0),
))
.spacing(10.0)
.align(HAlign::Leading)
})
.drive(
|d: Drive| async move { d.assert_frame("b", at("a", Some(0.0), Some(30.0))).await },
)
}
#[day_macros::test(day_core)]
fn row_alignment() -> Case {
Case::new()
.proves(kinds::CONTAINER)
.page(|| {
row((block(Color::rgb(1.0, 0.0, 0.0), day_core::id!(a), 50.0, 20.0), block(Color::rgb(0.0, 0.0, 1.0), day_core::id!(b), 50.0, 40.0)))
.spacing(8.0)
.align(VAlign::Center)
})
.drive(|d: Drive| async move {
d.assert_frame("b", at("a", Some(58.0), Some(-10.0))).await
})
}
#[day_macros::test(day_core)]
fn grid_columns() -> Case {
Case::new()
.proves(kinds::CONTAINER)
.page(|| {
grid((
grid_row((
block(Color::rgb(1.0, 0.0, 0.0), day_core::id!(a), 40.0, 20.0),
block(Color::rgb(0.0, 0.0, 1.0), day_core::id!(b), 60.0, 20.0),
)),
grid_row((
block(Color::rgb(0.0, 0.0, 1.0), day_core::id!(c), 30.0, 20.0),
block(Color::rgb(1.0, 0.0, 0.0), day_core::id!(d), 30.0, 20.0),
)),
))
.spacing(0.0)
.align(day_core::Alignment::TopLeading)
})
.drive(|d: Drive| async move {
d.assert_frame("d", at("a", Some(40.0), Some(20.0))).await?;
d.assert_frame("b", at("a", Some(40.0), Some(0.0))).await
})
}
#[day_macros::test(day_core)]
fn zstack_layers() -> Case {
Case::new()
.proves(kinds::CONTAINER)
.proves_cap(day_spec::Cap::Snapshot)
.page(|| {
zstack((
block(Color::rgb(1.0, 0.0, 0.0), day_core::id!(under), 100.0, 60.0),
block(Color::rgb(0.0, 0.0, 1.0), day_core::id!(over), 50.0, 30.0),
))
.id(day_core::id!(stack))
})
.shot("default")
.drive(|d: Drive| async move {
d.assert_frame("over", at("under", Some(25.0), Some(15.0)))
.await?;
d.sample_pixel("under", 0.5, 0.5, "#0000ff").await?;
d.sample_pixel("under", 0.05, 0.1, "#ff0000").await
})
}
#[day_macros::test(day_core)]
fn scroll_to_target() -> Case {
let jump = Signal::new(None::<ScrollTarget>);
Case::new()
.proves(kinds::SCROLL)
.proves_duty("scroll_to")
.proves_duty("set_scroll_content")
.page(move || {
let rows: Vec<AnyPiece> = (0..30)
.map(|i| {
let color = if i < 20 {
Color::rgb(1.0, 0.0, 0.0)
} else {
Color::rgb(0.0, 0.0, 1.0)
};
rectangle().fill(color).frame(120.0, 40.0).any()
})
.collect();
column((
scroll(column(rows).spacing(0.0))
.scroll_target(jump)
.id(day_core::id!(scroll))
.frame(120.0, 120.0),
button("Bottom")
.action(move || jump.set(Some(ScrollTarget::Bottom)))
.id(day_core::id!(bottom)),
))
.spacing(8.0)
})
.drive(|d: Drive| async move {
d.sample_pixel("scroll", 0.5, 0.5, "#ff0000").await?;
d.tap("bottom").await?;
d.sample_pixel("scroll", 0.5, 0.5, "#0000ff").await
})
}
}