use crate::core::Bounds;
use crate::core::Pixels;
use crate::gpu::Affine;
use crate::gpu::{TextAlign, TextTransform};
use crate::view::shadow::Shadows;
use crate::view::style::{Justify, Length, Sides, refine_options};
use crate::view::theme::ColorSpec;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TextRefine {
pub weight: Option<u16>,
pub align: Option<TextAlign>,
pub tracking: Option<f32>,
pub lines: Option<u16>,
pub transform: Option<TextTransform>,
pub leading: Option<f32>,
pub strike: Option<bool>,
pub mono: Option<bool>,
}
impl TextRefine {
fn refine(&mut self, other: &TextRefine) {
refine_options!(self, other, weight, align, tracking, lines, transform, leading, strike, mono);
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct CornerRadii {
pub top_left: Option<Pixels>,
pub top_right: Option<Pixels>,
pub bottom_right: Option<Pixels>,
pub bottom_left: Option<Pixels>,
}
impl CornerRadii {
fn refine(&mut self, other: &CornerRadii) {
refine_options!(self, other, top_left, top_right, bottom_right, bottom_left);
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct GridStyle {
pub cols: Option<u16>,
pub rows: Option<u16>,
pub col_span: Option<u16>,
pub row_span: Option<u16>,
pub col_start: Option<i16>,
pub row_start: Option<i16>,
pub flow_col: Option<bool>,
}
impl GridStyle {
fn refine(&mut self, other: &GridStyle) {
refine_options!(self, other, cols, rows, col_span, row_span, col_start, row_start, flow_col);
}
pub fn is_grid(&self) -> bool {
self.cols.is_some() || self.rows.is_some()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TransformSpec {
pub translate_x: Option<Length>,
pub translate_y: Option<Length>,
pub rotate: Option<f32>,
pub scale_x: Option<f32>,
pub scale_y: Option<f32>,
pub skew_x: Option<f32>,
pub skew_y: Option<f32>,
}
impl TransformSpec {
fn refine(&mut self, other: &TransformSpec) {
refine_options!(self, other, translate_x, translate_y, rotate, scale_x, scale_y, skew_x, skew_y);
}
pub fn is_none(&self) -> bool {
*self == Self::default()
}
pub fn affine(&self, bounds: Bounds) -> Affine {
let (width, height) = (bounds.size.width.0, bounds.size.height.0);
let center = (bounds.origin.x.0 + width * 0.5, bounds.origin.y.0 + height * 0.5);
let offset = |length: Option<Length>, extent: f32| match length {
Some(Length::Px(value)) => value.0,
Some(Length::Percent(fraction)) => fraction * extent,
Some(Length::Auto) | None => 0.0,
};
Affine::scale(self.scale_x.unwrap_or(1.0), self.scale_y.unwrap_or(1.0))
.then(Affine::skew(self.skew_x.unwrap_or(0.0), self.skew_y.unwrap_or(0.0)))
.then(Affine::rotate(self.rotate.unwrap_or(0.0)))
.about(center)
.then(Affine::translate(offset(self.translate_x, width), offset(self.translate_y, height)))
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct FilterSpec {
pub blur: Option<f32>,
pub backdrop_blur: Option<f32>,
}
impl FilterSpec {
fn refine(&mut self, other: &FilterSpec) {
refine_options!(self, other, blur, backdrop_blur);
}
}
pub const AUTO_TOP: u8 = 1;
pub const AUTO_RIGHT: u8 = 2;
pub const AUTO_BOTTOM: u8 = 4;
pub const AUTO_LEFT: u8 = 8;
pub const AUTO_ALL: u8 = AUTO_TOP | AUTO_RIGHT | AUTO_BOTTOM | AUTO_LEFT;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct StyleExtra {
pub text: TextRefine,
pub corners: CornerRadii,
pub border: Sides,
pub shadow: Option<Shadows>,
pub gradient: Option<(f32, ColorSpec)>,
pub ring: Option<Pixels>,
pub ring_color: Option<ColorSpec>,
pub reverse: Option<bool>,
pub align_content: Option<Justify>,
pub grid: GridStyle,
pub margin_auto: u8,
pub z: Option<i16>,
pub transform: TransformSpec,
pub filter: FilterSpec,
}
impl StyleExtra {
pub fn refine(&mut self, other: &StyleExtra) {
self.text.refine(&other.text);
self.corners.refine(&other.corners);
self.border.refine(&other.border);
self.grid.refine(&other.grid);
self.transform.refine(&other.transform);
self.filter.refine(&other.filter);
refine_options!(self, other, shadow, gradient, ring, ring_color, reverse, align_content, z);
self.margin_auto |= other.margin_auto;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::view::style::Style;
#[test]
fn un_estilo_sin_extras_solo_paga_un_puntero() {
assert_eq!(std::mem::size_of::<Option<Box<StyleExtra>>>(), std::mem::size_of::<usize>());
let style = Style::default();
assert!(style.extra.is_none(), "no se reserva nada hasta usar una propiedad poco frecuente");
}
#[test]
fn refinar_une_los_extras_sin_perder_los_del_estilo_base() {
let mut base = Style::default();
base.extra_mut().text.weight = Some(600);
base.extra_mut().margin_auto = AUTO_LEFT;
let mut other = Style::default();
other.extra_mut().text.align = Some(TextAlign::Center);
other.extra_mut().margin_auto = AUTO_RIGHT;
base.refine(&other);
let extra = base.extra.as_deref().unwrap();
assert_eq!((extra.text.weight, extra.text.align), (Some(600), Some(TextAlign::Center)));
assert_eq!(extra.margin_auto, AUTO_LEFT | AUTO_RIGHT);
let mut empty = Style::default();
empty.refine(&other);
assert_eq!(empty.extra, other.extra, "sin extras propios se copian los del otro");
}
}