takumi_css/style/properties/
background_position.rs1use crate::style::{ToCss, unexpected_token};
2use cssparser::{Parser, Token, match_ignore_ascii_case};
3use std::fmt;
4use taffy::{Point, Size};
5
6use super::background_image::parse_comma_list;
7use crate::style::{
8 Animatable, Color, CssSyntaxKind, CssToken, FromCss, Length, ListInterpolationStrategy,
9 MakeComputed, ParseResult, SizingContext, SpacePair, tw::TailwindPropertyParser,
10};
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum PositionKeywordX {
15 Left,
17 Center,
19 Right,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq)]
25pub enum PositionKeywordY {
26 Top,
28 Center,
30 Bottom,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq)]
36pub enum PositionComponent {
37 KeywordX(PositionKeywordX),
39 KeywordY(PositionKeywordY),
41 Length(Length),
43}
44
45impl MakeComputed for PositionComponent {
46 fn make_computed(&mut self, sizing: &SizingContext) {
47 if let Self::Length(length) = self {
48 length.make_computed(sizing);
49 }
50 }
51}
52
53impl Animatable for PositionComponent {
54 fn interpolate(
55 &mut self,
56 from: &Self,
57 to: &Self,
58 progress: f32,
59 sizing: &SizingContext,
60 current_color: Color,
61 ) {
62 let mut length = Length::from(*from);
63 length.interpolate(
64 &Length::from(*from),
65 &Length::from(*to),
66 progress,
67 sizing,
68 current_color,
69 );
70 *self = PositionComponent::Length(length);
71 }
72}
73
74impl From<Length> for PositionComponent {
75 fn from(value: Length) -> Self {
76 PositionComponent::Length(value)
77 }
78}
79
80impl From<PositionComponent> for Length {
81 fn from(component: PositionComponent) -> Self {
82 match component {
83 PositionComponent::KeywordX(keyword) => match keyword {
84 PositionKeywordX::Center => Self::Percentage(50.0),
85 PositionKeywordX::Left => Self::Percentage(0.0),
86 PositionKeywordX::Right => Self::Percentage(100.0),
87 },
88 PositionComponent::KeywordY(keyword) => match keyword {
89 PositionKeywordY::Center => Self::Percentage(50.0),
90 PositionKeywordY::Top => Self::Percentage(0.0),
91 PositionKeywordY::Bottom => Self::Percentage(100.0),
92 },
93 PositionComponent::Length(length) => length,
94 }
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq)]
100pub struct BackgroundPosition<const DEFAULT_TOP_LEFT: bool = true>(
101 pub SpacePair<PositionComponent>,
102);
103
104pub type ObjectPosition = BackgroundPosition<false>;
106pub type TransformOrigin = BackgroundPosition<false>;
108
109impl<const DEFAULT_TOP_LEFT: bool> MakeComputed for BackgroundPosition<DEFAULT_TOP_LEFT> {
110 fn make_computed(&mut self, sizing: &SizingContext) {
111 self.0.make_computed(sizing);
112 }
113}
114
115impl<const DEFAULT_TOP_LEFT: bool> Animatable for BackgroundPosition<DEFAULT_TOP_LEFT> {
116 fn list_interpolation_strategy() -> ListInterpolationStrategy {
117 ListInterpolationStrategy::RepeatToLcm
118 }
119
120 fn interpolate(
121 &mut self,
122 from: &Self,
123 to: &Self,
124 progress: f32,
125 sizing: &SizingContext,
126 current_color: Color,
127 ) {
128 let mut value = from.0;
129 value.interpolate(&from.0, &to.0, progress, sizing, current_color);
130 self.0 = value;
131 }
132}
133
134impl<const DEFAULT_TOP_LEFT: bool> BackgroundPosition<DEFAULT_TOP_LEFT> {
135 pub fn to_point(self, sizing: &SizingContext, border_box: Size<f32>) -> Point<f32> {
136 Point {
137 x: Length::from(self.0.x).to_px(sizing, border_box.width),
138 y: Length::from(self.0.y).to_px(sizing, border_box.height),
139 }
140 }
141}
142
143impl<const DEFAULT_TOP_LEFT: bool> TailwindPropertyParser for BackgroundPosition<DEFAULT_TOP_LEFT> {
144 fn parse_tw(token: &str) -> Option<Self> {
145 match token {
146 "top-left" => Some(Self(SpacePair::from_pair(
147 PositionComponent::KeywordX(PositionKeywordX::Left),
148 PositionComponent::KeywordY(PositionKeywordY::Top),
149 ))),
150 "top" => Some(Self(SpacePair::from_pair(
151 PositionComponent::KeywordX(PositionKeywordX::Center),
152 PositionComponent::KeywordY(PositionKeywordY::Top),
153 ))),
154 "top-right" => Some(Self(SpacePair::from_pair(
155 PositionComponent::KeywordX(PositionKeywordX::Right),
156 PositionComponent::KeywordY(PositionKeywordY::Top),
157 ))),
158 "left" => Some(Self(SpacePair::from_pair(
159 PositionComponent::KeywordX(PositionKeywordX::Left),
160 PositionComponent::KeywordY(PositionKeywordY::Center),
161 ))),
162 "center" => Some(Self(SpacePair::from_pair(
163 PositionComponent::KeywordX(PositionKeywordX::Center),
164 PositionComponent::KeywordY(PositionKeywordY::Center),
165 ))),
166 "right" => Some(Self(SpacePair::from_pair(
167 PositionComponent::KeywordX(PositionKeywordX::Right),
168 PositionComponent::KeywordY(PositionKeywordY::Center),
169 ))),
170 "bottom-left" => Some(Self(SpacePair::from_pair(
171 PositionComponent::KeywordX(PositionKeywordX::Left),
172 PositionComponent::KeywordY(PositionKeywordY::Bottom),
173 ))),
174 "bottom" => Some(Self(SpacePair::from_pair(
175 PositionComponent::KeywordX(PositionKeywordX::Center),
176 PositionComponent::KeywordY(PositionKeywordY::Bottom),
177 ))),
178 "bottom-right" => Some(Self(SpacePair::from_pair(
179 PositionComponent::KeywordX(PositionKeywordX::Right),
180 PositionComponent::KeywordY(PositionKeywordY::Bottom),
181 ))),
182 _ => None,
183 }
184 }
185}
186
187impl<const DEFAULT_TOP_LEFT: bool> Default for BackgroundPosition<DEFAULT_TOP_LEFT> {
188 fn default() -> Self {
189 if DEFAULT_TOP_LEFT {
190 Self(SpacePair::from_pair(
191 PositionComponent::KeywordX(PositionKeywordX::Left),
192 PositionComponent::KeywordY(PositionKeywordY::Top),
193 ))
194 } else {
195 Self(SpacePair::from_pair(
196 PositionComponent::KeywordX(PositionKeywordX::Center),
197 PositionComponent::KeywordY(PositionKeywordY::Center),
198 ))
199 }
200 }
201}
202
203impl<'i, const DEFAULT_TOP_LEFT: bool> FromCss<'i> for BackgroundPosition<DEFAULT_TOP_LEFT> {
204 fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
205 let first = PositionComponent::from_css(input)?;
206 let second = input.try_parse(PositionComponent::from_css).ok();
208
209 let (x, y) = match (first, second) {
210 (PositionComponent::KeywordY(_), None) => {
211 (PositionComponent::KeywordX(PositionKeywordX::Center), first)
212 }
213 (PositionComponent::KeywordY(_), Some(second)) => (second, first),
214 (x, None) => (x, PositionComponent::KeywordY(PositionKeywordY::Center)),
215 (x, Some(y)) => (x, y),
216 };
217
218 Ok(BackgroundPosition(SpacePair::from_pair(x, y)))
219 }
220
221 const VALID_TOKENS: &'static [CssToken] = PositionComponent::VALID_TOKENS;
222}
223
224impl<'i> FromCss<'i> for PositionComponent {
225 fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
226 if let Ok(v) = input.try_parse(Length::from_css) {
227 return Ok(v.into());
228 }
229
230 let location = input.current_source_location();
231 let token = input.next()?;
232 let Token::Ident(ident) = token else {
233 return Err(unexpected_token!(location, token));
234 };
235
236 match_ignore_ascii_case! {
237 &ident,
238 "left" => Ok(PositionComponent::KeywordX(PositionKeywordX::Left)),
239 "center" => Ok(PositionComponent::KeywordX(PositionKeywordX::Center)),
240 "right" => Ok(PositionComponent::KeywordX(PositionKeywordX::Right)),
241 "top" => Ok(PositionComponent::KeywordY(PositionKeywordY::Top)),
242 "bottom" => Ok(PositionComponent::KeywordY(PositionKeywordY::Bottom)),
243 _ => Err(unexpected_token!(location, token)),
244 }
245 }
246
247 const VALID_TOKENS: &'static [CssToken] = &[
248 CssToken::Keyword("left"),
249 CssToken::Keyword("center"),
250 CssToken::Keyword("right"),
251 CssToken::Keyword("top"),
252 CssToken::Keyword("bottom"),
253 CssToken::Syntax(CssSyntaxKind::Length),
254 ];
255}
256
257pub type BackgroundPositions = Box<[BackgroundPosition]>;
259
260impl<'i> FromCss<'i> for BackgroundPositions {
261 fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
262 parse_comma_list(input, BackgroundPosition::from_css)
263 }
264
265 const VALID_TOKENS: &'static [CssToken] = BackgroundPosition::<true>::VALID_TOKENS;
266}
267
268impl ToCss for PositionKeywordX {
269 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
270 match self {
271 Self::Left => dest.write_str("left"),
272 Self::Center => dest.write_str("center"),
273 Self::Right => dest.write_str("right"),
274 }
275 }
276}
277
278impl ToCss for PositionKeywordY {
279 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
280 match self {
281 Self::Top => dest.write_str("top"),
282 Self::Center => dest.write_str("center"),
283 Self::Bottom => dest.write_str("bottom"),
284 }
285 }
286}
287
288impl ToCss for PositionComponent {
289 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
290 match self {
291 Self::KeywordX(k) => k.to_css(dest),
292 Self::KeywordY(k) => k.to_css(dest),
293 Self::Length(l) => l.to_css(dest),
294 }
295 }
296}
297
298impl<const DEFAULT_TOP_LEFT: bool> ToCss for BackgroundPosition<DEFAULT_TOP_LEFT> {
299 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
300 self.0.to_css(dest)
301 }
302}