use crate::layout::style::{ToCss, unexpected_token};
use cssparser::{Parser, Token, match_ignore_ascii_case};
use std::fmt;
use taffy::{Point, Size};
use crate::{
layout::style::{
Animatable, Color, CssSyntaxKind, CssToken, FromCss, Length, ListInterpolationStrategy,
MakeComputed, ParseResult, SpacePair, tw::TailwindPropertyParser,
},
rendering::Sizing,
};
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum PositionKeywordX {
Left,
Center,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum PositionKeywordY {
Top,
Center,
Bottom,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum PositionComponent {
KeywordX(PositionKeywordX),
KeywordY(PositionKeywordY),
Length(Length),
}
impl MakeComputed for PositionComponent {
fn make_computed(&mut self, sizing: &Sizing) {
if let Self::Length(length) = self {
length.make_computed(sizing);
}
}
}
impl Animatable for PositionComponent {
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
sizing: &Sizing,
current_color: Color,
) {
let mut length = Length::from(*from);
length.interpolate(
&Length::from(*from),
&Length::from(*to),
progress,
sizing,
current_color,
);
*self = PositionComponent::Length(length);
}
}
impl From<Length> for PositionComponent {
fn from(value: Length) -> Self {
PositionComponent::Length(value)
}
}
impl From<PositionComponent> for Length {
fn from(component: PositionComponent) -> Self {
match component {
PositionComponent::KeywordX(keyword) => match keyword {
PositionKeywordX::Center => Self::Percentage(50.0),
PositionKeywordX::Left => Self::Percentage(0.0),
PositionKeywordX::Right => Self::Percentage(100.0),
},
PositionComponent::KeywordY(keyword) => match keyword {
PositionKeywordY::Center => Self::Percentage(50.0),
PositionKeywordY::Top => Self::Percentage(0.0),
PositionKeywordY::Bottom => Self::Percentage(100.0),
},
PositionComponent::Length(length) => length,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BackgroundPosition<const DEFAULT_TOP_LEFT: bool = true>(
pub SpacePair<PositionComponent>,
);
pub type ObjectPosition = BackgroundPosition<false>;
pub type TransformOrigin = BackgroundPosition<false>;
impl<const DEFAULT_TOP_LEFT: bool> MakeComputed for BackgroundPosition<DEFAULT_TOP_LEFT> {
fn make_computed(&mut self, sizing: &Sizing) {
self.0.make_computed(sizing);
}
}
impl<const DEFAULT_TOP_LEFT: bool> Animatable for BackgroundPosition<DEFAULT_TOP_LEFT> {
fn list_interpolation_strategy() -> ListInterpolationStrategy {
ListInterpolationStrategy::RepeatToLcm
}
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
sizing: &Sizing,
current_color: Color,
) {
let mut value = from.0;
value.interpolate(&from.0, &to.0, progress, sizing, current_color);
self.0 = value;
}
}
impl<const DEFAULT_TOP_LEFT: bool> BackgroundPosition<DEFAULT_TOP_LEFT> {
pub(crate) fn to_point(self, sizing: &Sizing, border_box: Size<f32>) -> Point<f32> {
Point {
x: Length::from(self.0.x).to_px(sizing, border_box.width),
y: Length::from(self.0.y).to_px(sizing, border_box.height),
}
}
}
impl<const DEFAULT_TOP_LEFT: bool> TailwindPropertyParser for BackgroundPosition<DEFAULT_TOP_LEFT> {
fn parse_tw(token: &str) -> Option<Self> {
match token {
"top-left" => Some(Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Left),
PositionComponent::KeywordY(PositionKeywordY::Top),
))),
"top" => Some(Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Center),
PositionComponent::KeywordY(PositionKeywordY::Top),
))),
"top-right" => Some(Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Right),
PositionComponent::KeywordY(PositionKeywordY::Top),
))),
"left" => Some(Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Left),
PositionComponent::KeywordY(PositionKeywordY::Center),
))),
"center" => Some(Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Center),
PositionComponent::KeywordY(PositionKeywordY::Center),
))),
"right" => Some(Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Right),
PositionComponent::KeywordY(PositionKeywordY::Center),
))),
"bottom-left" => Some(Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Left),
PositionComponent::KeywordY(PositionKeywordY::Bottom),
))),
"bottom" => Some(Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Center),
PositionComponent::KeywordY(PositionKeywordY::Bottom),
))),
"bottom-right" => Some(Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Right),
PositionComponent::KeywordY(PositionKeywordY::Bottom),
))),
_ => None,
}
}
}
impl<const DEFAULT_TOP_LEFT: bool> Default for BackgroundPosition<DEFAULT_TOP_LEFT> {
fn default() -> Self {
if DEFAULT_TOP_LEFT {
Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Left),
PositionComponent::KeywordY(PositionKeywordY::Top),
))
} else {
Self(SpacePair::from_pair(
PositionComponent::KeywordX(PositionKeywordX::Center),
PositionComponent::KeywordY(PositionKeywordY::Center),
))
}
}
}
impl<'i, const DEFAULT_TOP_LEFT: bool> FromCss<'i> for BackgroundPosition<DEFAULT_TOP_LEFT> {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
let first = PositionComponent::from_css(input)?;
let second = input.try_parse(PositionComponent::from_css).ok();
let (x, y) = match (first, second) {
(PositionComponent::KeywordY(_), None) => {
(PositionComponent::KeywordX(PositionKeywordX::Center), first)
}
(PositionComponent::KeywordY(_), Some(second)) => (second, first),
(x, None) => (x, PositionComponent::KeywordY(PositionKeywordY::Center)),
(x, Some(y)) => (x, y),
};
Ok(BackgroundPosition(SpacePair::from_pair(x, y)))
}
const VALID_TOKENS: &'static [CssToken] = PositionComponent::VALID_TOKENS;
}
impl<'i> FromCss<'i> for PositionComponent {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
if let Ok(v) = input.try_parse(Length::from_css) {
return Ok(v.into());
}
let location = input.current_source_location();
let token = input.next()?;
let Token::Ident(ident) = token else {
return Err(unexpected_token!(location, token));
};
match_ignore_ascii_case! {
&ident,
"left" => Ok(PositionComponent::KeywordX(PositionKeywordX::Left)),
"center" => Ok(PositionComponent::KeywordX(PositionKeywordX::Center)),
"right" => Ok(PositionComponent::KeywordX(PositionKeywordX::Right)),
"top" => Ok(PositionComponent::KeywordY(PositionKeywordY::Top)),
"bottom" => Ok(PositionComponent::KeywordY(PositionKeywordY::Bottom)),
_ => Err(unexpected_token!(location, token)),
}
}
const VALID_TOKENS: &'static [CssToken] = &[
CssToken::Keyword("left"),
CssToken::Keyword("center"),
CssToken::Keyword("right"),
CssToken::Keyword("top"),
CssToken::Keyword("bottom"),
CssToken::Syntax(CssSyntaxKind::Length),
];
}
pub type BackgroundPositions = Box<[BackgroundPosition]>;
impl<'i> FromCss<'i> for BackgroundPositions {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
let mut values = Vec::new();
values.push(BackgroundPosition::from_css(input)?);
while input.expect_comma().is_ok() {
values.push(BackgroundPosition::from_css(input)?);
}
Ok(values.into_boxed_slice())
}
const VALID_TOKENS: &'static [CssToken] = BackgroundPosition::<true>::VALID_TOKENS;
}
impl ToCss for PositionKeywordX {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
match self {
Self::Left => dest.write_str("left"),
Self::Center => dest.write_str("center"),
Self::Right => dest.write_str("right"),
}
}
}
impl ToCss for PositionKeywordY {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
match self {
Self::Top => dest.write_str("top"),
Self::Center => dest.write_str("center"),
Self::Bottom => dest.write_str("bottom"),
}
}
}
impl ToCss for PositionComponent {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
match self {
Self::KeywordX(k) => k.to_css(dest),
Self::KeywordY(k) => k.to_css(dest),
Self::Length(l) => l.to_css(dest),
}
}
}
impl<const DEFAULT_TOP_LEFT: bool> ToCss for BackgroundPosition<DEFAULT_TOP_LEFT> {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
self.0.to_css(dest)
}
}