use std::{fmt, mem, sync::Arc};
use zng_app_context::context_local;
use zng_color::{colors, Hsla, Hsva, Rgba};
use zng_layout::{
context::{LayoutMask, LAYOUT},
unit::{
Factor, FactorPercent, FactorSideOffsets, FactorUnits, Layout2d, Length, PxCornerRadius, PxPoint, PxRect, PxSideOffsets, PxSize,
Size,
},
};
use zng_var::{
animation::{easing::EasingStep, Transitionable},
context_var, impl_from_and_into_var, Var,
};
pub use zng_view_api::LineOrientation;
use crate::widget::VarLayout;
use super::{info::WidgetBorderInfo, WidgetId, WIDGET};
#[derive(Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum LineStyle {
Solid,
Double,
Dotted,
Dashed,
Groove,
Ridge,
Wavy(f32),
Hidden,
}
impl fmt::Debug for LineStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "LineStyle::")?;
}
match self {
LineStyle::Solid => write!(f, "Solid"),
LineStyle::Double => write!(f, "Double"),
LineStyle::Dotted => write!(f, "Dotted"),
LineStyle::Dashed => write!(f, "Dashed"),
LineStyle::Groove => write!(f, "Groove"),
LineStyle::Ridge => write!(f, "Ridge"),
LineStyle::Wavy(t) => write!(f, "Wavy({t})"),
LineStyle::Hidden => write!(f, "Hidden"),
}
}
}
impl Transitionable for LineStyle {
fn lerp(self, to: &Self, step: EasingStep) -> Self {
match (self, *to) {
(Self::Wavy(a), Self::Wavy(b)) => Self::Wavy(a.lerp(&b, step)),
(a, b) => {
if step < 1.fct() {
a
} else {
b
}
}
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Hash, Eq, serde::Serialize, serde::Deserialize)]
pub enum BorderStyle {
Solid = 1,
Double = 2,
Dotted = 3,
Dashed = 4,
Hidden = 5,
Groove = 6,
Ridge = 7,
Inset = 8,
Outset = 9,
}
impl fmt::Debug for BorderStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "BorderStyle::")?;
}
match self {
BorderStyle::Solid => write!(f, "Solid"),
BorderStyle::Double => write!(f, "Double"),
BorderStyle::Dotted => write!(f, "Dotted"),
BorderStyle::Dashed => write!(f, "Dashed"),
BorderStyle::Groove => write!(f, "Groove"),
BorderStyle::Ridge => write!(f, "Ridge"),
BorderStyle::Hidden => write!(f, "Hidden"),
BorderStyle::Inset => write!(f, "Inset"),
BorderStyle::Outset => write!(f, "Outset"),
}
}
}
impl From<BorderStyle> for zng_view_api::BorderStyle {
fn from(s: BorderStyle) -> Self {
match s {
BorderStyle::Solid => zng_view_api::BorderStyle::Solid,
BorderStyle::Double => zng_view_api::BorderStyle::Double,
BorderStyle::Dotted => zng_view_api::BorderStyle::Dotted,
BorderStyle::Dashed => zng_view_api::BorderStyle::Dashed,
BorderStyle::Hidden => zng_view_api::BorderStyle::Hidden,
BorderStyle::Groove => zng_view_api::BorderStyle::Groove,
BorderStyle::Ridge => zng_view_api::BorderStyle::Ridge,
BorderStyle::Inset => zng_view_api::BorderStyle::Inset,
BorderStyle::Outset => zng_view_api::BorderStyle::Outset,
}
}
}
impl Transitionable for BorderStyle {
fn lerp(self, to: &Self, step: EasingStep) -> Self {
if step < 1.fct() {
self
} else {
*to
}
}
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, Transitionable)]
pub struct BorderSide {
pub color: Rgba,
pub style: BorderStyle,
}
impl fmt::Debug for BorderSide {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("BorderSide")
.field("color", &self.color)
.field("style", &self.style)
.finish()
} else {
if let BorderStyle::Hidden = self.style {
if self.color.alpha.abs() < 0.0001 {
return write!(f, "Hidden");
}
}
write!(f, "({:?}, {:?})", self.color, self.style)
}
}
}
impl BorderSide {
pub fn new<C: Into<Rgba>, S: Into<BorderStyle>>(color: C, style: S) -> Self {
BorderSide {
color: color.into(),
style: style.into(),
}
}
pub fn solid<C: Into<Rgba>>(color: C) -> Self {
Self::new(color, BorderStyle::Solid)
}
pub fn double<C: Into<Rgba>>(color: C) -> Self {
Self::new(color, BorderStyle::Double)
}
pub fn dotted<C: Into<Rgba>>(color: C) -> Self {
Self::new(color, BorderStyle::Dotted)
}
pub fn dashed<C: Into<Rgba>>(color: C) -> Self {
Self::new(color, BorderStyle::Dashed)
}
pub fn groove<C: Into<Rgba>>(color: C) -> Self {
Self::new(color, BorderStyle::Groove)
}
pub fn ridge<C: Into<Rgba>>(color: C) -> Self {
Self::new(color, BorderStyle::Ridge)
}
pub fn inset<C: Into<Rgba>>(color: C) -> Self {
Self::new(color, BorderStyle::Inset)
}
pub fn outset<C: Into<Rgba>>(color: C) -> Self {
Self::new(color, BorderStyle::Outset)
}
pub fn hidden() -> Self {
Self::new(colors::BLACK.transparent(), BorderStyle::Hidden)
}
}
impl From<BorderSide> for zng_view_api::BorderSide {
fn from(s: BorderSide) -> Self {
zng_view_api::BorderSide {
color: s.color,
style: s.style.into(),
}
}
}
impl Default for BorderSide {
fn default() -> Self {
Self::hidden()
}
}
#[derive(Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, Transitionable)]
pub struct CornerRadius {
pub top_left: Size,
pub top_right: Size,
pub bottom_right: Size,
pub bottom_left: Size,
}
impl fmt::Debug for CornerRadius {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("BorderRadius")
.field("top_left", &self.top_left)
.field("top_right", &self.top_right)
.field("bottom_right", &self.bottom_right)
.field("bottom_left", &self.bottom_left)
.finish()
} else if self.all_corners_eq() {
write!(f, "{:?}", self.top_left)
} else {
write!(
f,
"({:?}, {:?}, {:?}, {:?})",
self.top_left, self.top_right, self.bottom_right, self.bottom_left
)
}
}
}
impl CornerRadius {
pub fn new<TL: Into<Size>, TR: Into<Size>, BR: Into<Size>, BL: Into<Size>>(
top_left: TL,
top_right: TR,
bottom_right: BR,
bottom_left: BL,
) -> Self {
CornerRadius {
top_left: top_left.into(),
top_right: top_right.into(),
bottom_right: bottom_right.into(),
bottom_left: bottom_left.into(),
}
}
pub fn new_all<E: Into<Size>>(ellipse: E) -> Self {
let e = ellipse.into();
CornerRadius {
top_left: e.clone(),
top_right: e.clone(),
bottom_left: e.clone(),
bottom_right: e,
}
}
pub fn zero() -> Self {
Self::new_all(Size::zero())
}
pub fn all_corners_eq(&self) -> bool {
self.top_left == self.top_right && self.top_left == self.bottom_right && self.top_left == self.bottom_left
}
}
impl Layout2d for CornerRadius {
type Px = PxCornerRadius;
fn layout_dft(&self, default: Self::Px) -> Self::Px {
PxCornerRadius {
top_left: self.top_left.layout_dft(default.top_left),
top_right: self.top_right.layout_dft(default.top_right),
bottom_left: self.bottom_left.layout_dft(default.bottom_left),
bottom_right: self.bottom_right.layout_dft(default.bottom_right),
}
}
fn affect_mask(&self) -> LayoutMask {
self.top_left.affect_mask() | self.top_right.affect_mask() | self.bottom_left.affect_mask() | self.bottom_right.affect_mask()
}
}
impl_from_and_into_var! {
fn from(all: Size) -> CornerRadius {
CornerRadius::new_all(all)
}
fn from(all: Length) -> CornerRadius {
CornerRadius::new_all(all)
}
fn from(percent: FactorPercent) -> CornerRadius {
CornerRadius::new_all(percent)
}
fn from(norm: Factor) -> CornerRadius {
CornerRadius::new_all(norm)
}
fn from(f: f32) -> CornerRadius {
CornerRadius::new_all(f)
}
fn from(i: i32) -> CornerRadius {
CornerRadius::new_all(i)
}
fn from<TL: Into<Size>, TR: Into<Size>, BR: Into<Size>, BL: Into<Size>>(
(top_left, top_right, bottom_right, bottom_left): (TL, TR, BR, BL),
) -> CornerRadius {
CornerRadius::new(top_left, top_right, bottom_right, bottom_left)
}
fn from(corner_radius: PxCornerRadius) -> CornerRadius {
CornerRadius::new(
corner_radius.top_left,
corner_radius.top_right,
corner_radius.bottom_right,
corner_radius.bottom_left,
)
}
}
#[derive(Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, Transitionable)]
pub struct BorderSides {
pub left: BorderSide,
pub right: BorderSide,
pub top: BorderSide,
pub bottom: BorderSide,
}
impl fmt::Debug for BorderSides {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("BorderSides")
.field("left", &self.left)
.field("right", &self.right)
.field("top", &self.top)
.field("bottom", &self.bottom)
.finish()
} else if self.all_eq() {
write!(f, "{:?}", self.top)
} else if self.dimensions_eq() {
write!(f, "({:?}, {:?})", self.top, self.left)
} else {
write!(f, "({:?}, {:?}, {:?}, {:?})", self.top, self.right, self.bottom, self.left)
}
}
}
impl BorderSides {
pub fn new_all<S: Into<BorderSide>>(side: S) -> Self {
let side = side.into();
BorderSides {
left: side,
right: side,
top: side,
bottom: side,
}
}
pub fn new_vh<TB: Into<BorderSide>, LR: Into<BorderSide>>(top_bottom: TB, left_right: LR) -> Self {
let top_bottom = top_bottom.into();
let left_right = left_right.into();
BorderSides {
left: left_right,
right: left_right,
top: top_bottom,
bottom: top_bottom,
}
}
pub fn new<T: Into<BorderSide>, R: Into<BorderSide>, B: Into<BorderSide>, L: Into<BorderSide>>(
top: T,
right: R,
bottom: B,
left: L,
) -> Self {
BorderSides {
left: left.into(),
right: right.into(),
top: top.into(),
bottom: bottom.into(),
}
}
pub fn new_top<T: Into<BorderSide>>(top: T) -> Self {
Self::new(top, BorderSide::hidden(), BorderSide::hidden(), BorderSide::hidden())
}
pub fn new_right<R: Into<BorderSide>>(right: R) -> Self {
Self::new(BorderSide::hidden(), right, BorderSide::hidden(), BorderSide::hidden())
}
pub fn new_bottom<B: Into<BorderSide>>(bottom: B) -> Self {
Self::new(BorderSide::hidden(), BorderSide::hidden(), bottom, BorderSide::hidden())
}
pub fn new_left<L: Into<BorderSide>>(left: L) -> Self {
Self::new(BorderSide::hidden(), BorderSide::hidden(), BorderSide::hidden(), left)
}
pub fn solid<C: Into<Rgba>>(color: C) -> Self {
Self::new_all(BorderSide::solid(color))
}
pub fn double<C: Into<Rgba>>(color: C) -> Self {
Self::new_all(BorderSide::double(color))
}
pub fn dotted<C: Into<Rgba>>(color: C) -> Self {
Self::new_all(BorderSide::dotted(color))
}
pub fn dashed<C: Into<Rgba>>(color: C) -> Self {
Self::new_all(BorderSide::dashed(color))
}
pub fn groove<C: Into<Rgba>>(color: C) -> Self {
Self::new_all(BorderSide::groove(color))
}
pub fn ridge<C: Into<Rgba>>(color: C) -> Self {
Self::new_all(BorderSide::ridge(color))
}
pub fn inset<C: Into<Rgba>>(color: C) -> Self {
Self::new_all(BorderSide::inset(color))
}
pub fn outset<C: Into<Rgba>>(color: C) -> Self {
Self::new_all(BorderSide::outset(color))
}
pub fn hidden() -> Self {
Self::new_all(BorderSide::hidden())
}
pub fn all_eq(&self) -> bool {
self.top == self.bottom && self.top == self.left && self.top == self.right
}
pub fn dimensions_eq(&self) -> bool {
self.top == self.bottom && self.left == self.right
}
}
impl Default for BorderSides {
fn default() -> Self {
Self::hidden()
}
}
impl_from_and_into_var! {
fn from(color: Rgba) -> BorderSide {
BorderSide::solid(color)
}
fn from(color: Hsva) -> BorderSide {
BorderSide::solid(color)
}
fn from(color: Hsla) -> BorderSide {
BorderSide::solid(color)
}
fn from(color: Rgba) -> BorderSides {
BorderSides::new_all(color)
}
fn from(color: Hsva) -> BorderSides {
BorderSides::new_all(color)
}
fn from(color: Hsla) -> BorderSides {
BorderSides::new_all(color)
}
fn from(style: BorderStyle) -> BorderSide {
BorderSide::new(colors::BLACK.transparent(), style)
}
fn from(style: BorderStyle) -> BorderSides {
BorderSides::new_all(style)
}
fn from<C: Into<Rgba>, S: Into<BorderStyle>>((color, style): (C, S)) -> BorderSide {
BorderSide::new(color, style)
}
fn from<C: Into<Rgba>, S: Into<BorderStyle>>((color, style): (C, S)) -> BorderSides {
BorderSides::new_all(BorderSide::new(color, style))
}
fn from<T: Into<BorderSide>, R: Into<BorderSide>, B: Into<BorderSide>, L: Into<BorderSide>>(
(top, right, bottom, left): (T, R, B, L),
) -> BorderSides {
BorderSides::new(top, right, bottom, left)
}
fn from<TB: Into<Rgba>, LR: Into<Rgba>, S: Into<BorderStyle>>((top_bottom, left_right, style): (TB, LR, S)) -> BorderSides {
let style = style.into();
BorderSides::new_vh((top_bottom, style), (left_right, style))
}
fn from<T: Into<Rgba>, R: Into<Rgba>, B: Into<Rgba>, L: Into<Rgba>, S: Into<BorderStyle>>(
(top, right, bottom, left, style): (T, R, B, L, S),
) -> BorderSides {
let style = style.into();
BorderSides::new((top, style), (right, style), (bottom, style), (left, style))
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum CornerRadiusFit {
None,
Widget,
#[default]
Tree,
}
impl fmt::Debug for CornerRadiusFit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "CornerRadiusFit::")?;
}
match self {
Self::None => write!(f, "None"),
Self::Widget => write!(f, "Widget"),
Self::Tree => write!(f, "Tree"),
}
}
}
context_var! {
pub static BORDER_ALIGN_VAR: FactorSideOffsets = FactorSideOffsets::zero();
pub static BORDER_OVER_VAR: bool = true;
pub static CORNER_RADIUS_VAR: CornerRadius = CornerRadius::zero();
pub static CORNER_RADIUS_FIT_VAR: CornerRadiusFit = CornerRadiusFit::default();
}
pub struct BORDER;
impl BORDER {
pub fn border_offsets(&self) -> PxSideOffsets {
let data = BORDER_DATA.get();
if data.widget_id == WIDGET.try_id() {
data.wgt_offsets
} else {
PxSideOffsets::zero()
}
}
pub fn inner_offsets(&self) -> PxSideOffsets {
let data = BORDER_DATA.get();
if data.widget_id == WIDGET.try_id() {
data.wgt_inner_offsets
} else {
PxSideOffsets::zero()
}
}
pub fn border_radius(&self) -> PxCornerRadius {
match CORNER_RADIUS_FIT_VAR.get() {
CornerRadiusFit::Tree => BORDER_DATA.get().border_radius(),
CornerRadiusFit::Widget => {
let data = BORDER_DATA.get();
if data.widget_id == Some(WIDGET.id()) {
data.border_radius()
} else {
CORNER_RADIUS_VAR.layout()
}
}
_ => CORNER_RADIUS_VAR.layout(),
}
}
pub fn inner_radius(&self) -> PxCornerRadius {
match CORNER_RADIUS_FIT_VAR.get() {
CornerRadiusFit::Tree => BORDER_DATA.get().inner_radius(),
CornerRadiusFit::Widget => {
let data = BORDER_DATA.get();
if data.widget_id == WIDGET.try_id() {
data.inner_radius()
} else {
CORNER_RADIUS_VAR.layout()
}
}
_ => CORNER_RADIUS_VAR.layout(),
}
}
pub fn outer_radius(&self) -> PxCornerRadius {
BORDER_DATA.get().corner_radius
}
pub fn fill_bounds(&self) -> (PxRect, PxCornerRadius) {
let align = BORDER_ALIGN_VAR.get();
let fill_size = LAYOUT.constraints().fill_size();
let inner_offsets = self.inner_offsets();
if align == FactorSideOffsets::zero() {
let fill_size = PxSize::new(
fill_size.width + inner_offsets.horizontal(),
fill_size.height + inner_offsets.vertical(),
);
return (PxRect::from_size(fill_size), self.outer_radius());
} else if align == FactorSideOffsets::new_all(1.0.fct()) {
return (
PxRect::new(PxPoint::new(inner_offsets.left, inner_offsets.top), fill_size),
self.inner_radius(),
);
}
let outer = self.outer_radius();
let inner = self.inner_radius();
let b_align = FactorSideOffsets {
top: 1.0.fct() - align.top,
right: 1.0.fct() - align.right,
bottom: 1.0.fct() - align.bottom,
left: 1.0.fct() - align.left,
};
let bounds = PxRect {
origin: PxPoint::new(inner_offsets.left * (align.left), inner_offsets.top * align.top),
size: PxSize::new(
fill_size.width + inner_offsets.left * b_align.left + inner_offsets.right * b_align.right,
fill_size.height + inner_offsets.top * b_align.top + inner_offsets.bottom * b_align.bottom,
),
};
let radius = PxCornerRadius {
top_left: PxSize::new(
outer.top_left.width.lerp(&inner.top_left.width, align.left),
outer.top_left.height.lerp(&inner.top_left.height, align.top),
),
top_right: PxSize::new(
outer.top_right.width.lerp(&inner.top_right.width, align.right),
outer.top_right.height.lerp(&inner.top_right.height, align.top),
),
bottom_left: PxSize::new(
outer.bottom_left.width.lerp(&inner.bottom_left.width, align.left),
outer.bottom_left.height.lerp(&inner.bottom_left.height, align.bottom),
),
bottom_right: PxSize::new(
outer.bottom_right.width.lerp(&inner.bottom_right.width, align.right),
outer.bottom_right.height.lerp(&inner.bottom_right.height, align.bottom),
),
};
(bounds, radius)
}
pub(super) fn with_inner(&self, f: impl FnOnce() -> PxSize) -> PxSize {
let mut data = BORDER_DATA.get_clone();
let border = WIDGET.border();
data.add_inner(&border);
BORDER_DATA.with_context(&mut Some(Arc::new(data)), || {
let corner_radius = BORDER.border_radius();
border.set_corner_radius(corner_radius);
border.set_offsets(PxSideOffsets::zero());
f()
})
}
pub fn measure_border(&self, offsets: PxSideOffsets, f: impl FnOnce() -> PxSize) -> PxSize {
let mut data = BORDER_DATA.get_clone();
data.add_offset(None, offsets);
BORDER_DATA.with_context(&mut Some(Arc::new(data)), f)
}
pub fn layout_border(&self, offsets: PxSideOffsets, f: impl FnOnce()) {
let mut data = BORDER_DATA.get_clone();
data.add_offset(Some(&WIDGET.border()), offsets);
BORDER_DATA.with_context(&mut Some(Arc::new(data)), f);
}
pub fn with_corner_radius<R>(&self, f: impl FnOnce() -> R) -> R {
let mut data = BORDER_DATA.get_clone();
data.set_corner_radius();
BORDER_DATA.with_context(&mut Some(Arc::new(data)), f)
}
pub fn border_layout(&self) -> (PxRect, PxSideOffsets) {
BORDER_LAYOUT.get().unwrap_or_else(|| {
#[cfg(debug_assertions)]
tracing::error!("the `border_layout` is only available inside the layout and render methods of the border visual node");
(PxRect::zero(), PxSideOffsets::zero())
})
}
pub fn with_border_layout(&self, rect: PxRect, offsets: PxSideOffsets, f: impl FnOnce()) {
BORDER_LAYOUT.with_context(&mut Some(Arc::new(Some((rect, offsets)))), f)
}
}
context_local! {
static BORDER_DATA: BorderOffsetsData = BorderOffsetsData::default();
static BORDER_LAYOUT: Option<(PxRect, PxSideOffsets)> = None;
}
#[derive(Debug, Clone, Default)]
struct BorderOffsetsData {
widget_id: Option<WidgetId>,
wgt_offsets: PxSideOffsets,
wgt_inner_offsets: PxSideOffsets,
eval_cr: bool,
corner_radius: PxCornerRadius,
cr_offsets: PxSideOffsets,
cr_inner_offsets: PxSideOffsets,
}
impl BorderOffsetsData {
fn add_offset(&mut self, layout_info: Option<&WidgetBorderInfo>, offset: PxSideOffsets) {
let widget_id = Some(WIDGET.id());
let is_wgt_start = self.widget_id != widget_id;
if is_wgt_start {
self.widget_id = widget_id;
self.wgt_offsets = PxSideOffsets::zero();
self.wgt_inner_offsets = PxSideOffsets::zero();
self.eval_cr |= layout_info.is_some() && matches!(CORNER_RADIUS_FIT_VAR.get(), CornerRadiusFit::Widget);
}
self.wgt_offsets = self.wgt_inner_offsets;
self.wgt_inner_offsets += offset;
if mem::take(&mut self.eval_cr) {
self.corner_radius = CORNER_RADIUS_VAR.layout();
self.cr_offsets = PxSideOffsets::zero();
self.cr_inner_offsets = PxSideOffsets::zero();
}
self.cr_offsets = self.cr_inner_offsets;
self.cr_inner_offsets += offset;
if let Some(border) = layout_info {
if is_wgt_start {
border.set_corner_radius(self.corner_radius);
}
border.set_offsets(self.wgt_inner_offsets);
}
}
fn add_inner(&mut self, layout_info: &WidgetBorderInfo) {
self.add_offset(Some(layout_info), PxSideOffsets::zero());
}
fn set_corner_radius(&mut self) {
self.eval_cr = matches!(CORNER_RADIUS_FIT_VAR.get(), CornerRadiusFit::Tree);
}
fn border_radius(&self) -> PxCornerRadius {
self.corner_radius.deflate(self.cr_offsets)
}
fn inner_radius(&self) -> PxCornerRadius {
self.corner_radius.deflate(self.cr_inner_offsets)
}
}