use std::{fmt, mem, ops};
use zng_layout::context::LayoutMask;
use zng_var::animation::{Transitionable, easing::EasingStep};
use zng_wgt::prelude::*;
#[derive(Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StackDirection {
pub place: Point,
pub origin: Point,
pub is_rtl_aware: bool,
}
impl fmt::Debug for StackDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("StackDirection")
.field("place", &self.place)
.field("origin", &self.origin)
.field("is_rtl_aware", &self.is_rtl_aware)
.finish()
} else if self.is_rtl_aware {
write!(f, "({:?}, {:?}, {:?})", self.place, self.origin, self.is_rtl_aware)
} else {
write!(f, "({:?}, {:?})", self.place, self.origin)
}
}
}
impl fmt::Display for StackDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(p) = f.precision() {
write!(f, "({:.p$}, {:.p$}", self.place, self.origin, p = p)?;
} else {
write!(f, "({}, {}", self.place, self.origin)?;
}
if self.is_rtl_aware {
write!(f, ", {})", self.is_rtl_aware)
} else {
write!(f, ")")
}
}
}
impl StackDirection {
pub fn new<P: Into<Point>, O: Into<Point>>(place: P, origin: O, is_rtl_aware: bool) -> Self {
Self {
place: place.into(),
origin: origin.into(),
is_rtl_aware,
}
}
pub fn left_to_right() -> Self {
Self {
place: (100.pct(), 0).into(),
origin: (0, 0).into(),
is_rtl_aware: false,
}
}
pub fn right_to_left() -> Self {
Self {
place: (0, 0).into(),
origin: (100.pct(), 0).into(),
is_rtl_aware: false,
}
}
pub fn start_to_end() -> Self {
Self {
place: (100.pct(), 0).into(),
origin: (0, 0).into(),
is_rtl_aware: true,
}
}
pub fn end_to_start() -> Self {
Self {
place: (0, 0).into(),
origin: (100.pct(), 0).into(),
is_rtl_aware: true,
}
}
pub fn top_to_bottom() -> Self {
Self {
place: (0, 100.pct()).into(),
origin: (0, 0).into(),
is_rtl_aware: false,
}
}
pub fn bottom_to_top() -> Self {
Self {
place: (0, 0).into(),
origin: (0, 100.pct()).into(),
is_rtl_aware: false,
}
}
pub fn none() -> Self {
Self {
place: Point::zero(),
origin: Point::zero(),
is_rtl_aware: false,
}
}
pub fn layout(&self, prev_item: PxRect, next_item: PxSize) -> PxVector {
if self.is_rtl_aware && LAYOUT.direction().is_rtl() {
let mut d = self.clone();
mem::swap(&mut d.place.x, &mut d.origin.x);
d.is_rtl_aware = false;
return d.layout_resolved_rtl(prev_item, next_item);
}
self.layout_resolved_rtl(prev_item, next_item)
}
pub(crate) fn layout_resolved_rtl(&self, prev_item: PxRect, next_item: PxSize) -> PxVector {
let c = LAYOUT.constraints();
let place = LAYOUT.with_constraints(c.with_exact_size(prev_item.size), || self.place.layout());
let origin = LAYOUT.with_constraints(c.with_exact_size(next_item), || self.origin.layout());
prev_item.origin.to_vector() + place.to_vector() - origin.to_vector()
}
pub fn direction_factor(&self, direction: LayoutDirection) -> Factor2d {
let size = PxSize::new(Px(1000), Px(1000));
let metrics = LayoutMetrics::new(1.fct(), size, Px(1000)).with_direction(direction);
let p = LAYOUT.with_context(metrics, || self.layout(PxRect::from_size(size), size));
fn v(px: Px) -> Factor {
(px.0 as f32 / 1000.0).fct()
}
(v(p.x), v(p.y)).into()
}
pub fn direction_scale(&self) -> Factor2d {
let scale = self.direction_factor(LayoutDirection::LTR).abs().yx();
if scale.x == 0.fct() && scale.y == 0.fct() {
return Factor2d::new(1.0, 1.0);
}
let angle = scale.y.0.atan2(scale.x.0).to_degrees();
if angle <= 20.0 {
Factor2d::new(1.0 - angle / 20.0, 0.0)
} else if angle >= 70.0 {
Factor2d::new(0.0, (angle - 70.0) / 20.0)
} else {
Factor2d::new(0.0, 0.0)
}
}
pub fn affect_mask(&self) -> LayoutMask {
self.place.affect_mask() | self.origin.affect_mask()
}
pub fn is_default(&self) -> bool {
self.place.is_default() && self.origin.is_default()
}
}
impl_from_and_into_var! {
fn from<P: Into<Point>, O: Into<Point>>((origin, size): (P, O)) -> StackDirection {
(origin, size, false).into()
}
fn from<P: Into<Point>, O: Into<Point>>((origin, size, rtl_aware): (P, O, bool)) -> StackDirection {
StackDirection::new(origin, size, rtl_aware)
}
}
impl<S: Into<Factor2d>> ops::Mul<S> for StackDirection {
type Output = Self;
fn mul(mut self, rhs: S) -> Self {
self *= rhs;
self
}
}
impl<S: Into<Factor2d>> ops::Mul<S> for &StackDirection {
type Output = StackDirection;
fn mul(self, rhs: S) -> Self::Output {
self.clone() * rhs
}
}
impl<S: Into<Factor2d>> ops::MulAssign<S> for StackDirection {
fn mul_assign(&mut self, rhs: S) {
let rhs = rhs.into();
self.place *= rhs;
self.origin *= rhs;
}
}
impl<S: Into<Factor2d>> ops::Div<S> for StackDirection {
type Output = Self;
fn div(mut self, rhs: S) -> Self {
self /= rhs;
self
}
}
impl<S: Into<Factor2d>> ops::Div<S> for &StackDirection {
type Output = StackDirection;
fn div(self, rhs: S) -> Self::Output {
self.clone() / rhs
}
}
impl<S: Into<Factor2d>> ops::DivAssign<S> for StackDirection {
fn div_assign(&mut self, rhs: S) {
let rhs = rhs.into();
self.place /= rhs;
self.origin /= rhs;
}
}
impl ops::Add for StackDirection {
type Output = Self;
fn add(mut self, rhs: Self) -> Self {
self += rhs;
self
}
}
impl ops::AddAssign for StackDirection {
fn add_assign(&mut self, rhs: Self) {
self.place += rhs.place;
self.origin += rhs.origin;
}
}
impl ops::Sub for StackDirection {
type Output = Self;
fn sub(mut self, rhs: Self) -> Self {
self -= rhs;
self
}
}
impl ops::SubAssign for StackDirection {
fn sub_assign(&mut self, rhs: Self) {
self.place -= rhs.place;
self.origin -= rhs.origin;
}
}
impl Transitionable for StackDirection {
fn lerp(self, to: &Self, step: EasingStep) -> Self {
Self {
place: self.place.lerp(&to.place, step),
origin: self.origin.lerp(&to.origin, step),
is_rtl_aware: if step < 1.fct() { self.is_rtl_aware } else { to.is_rtl_aware },
}
}
}