hobo_css/
lib.rs

1pub mod prelude;
2#[macro_use] pub mod units;
3#[macro_use] pub mod properties;
4#[doc(hidden)] #[macro_use] mod shortcuts;
5#[macro_use] pub mod selector;
6mod append_property;
7/// Css named colors
8pub mod color;
9pub mod font_face;
10pub mod media;
11
12pub use append_property::AppendProperty;
13pub use color::Color;
14pub use hobo_css_macros as macros;
15pub use macros::AppendProperty;
16#[doc(hidden)] pub use paste;
17pub use properties::*;
18pub use units::{Unit, F32};
19
20// #[extend::ext(pub)]
21// impl F32 {
22//     fn new_unwrap(x: f32) -> Self { F32::new(x).unwrap() }
23// }
24
25#[derive(Debug, PartialEq, Eq, Hash, Clone)]
26pub enum Rule {
27	Style(StyleRule),
28	Media(media::MediaSelector, Style),
29	// Keyframes,
30	FontFace(font_face::FontFace),
31}
32
33impl std::fmt::Display for Rule {
34	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35		match self {
36			Self::Style(x) => x.fmt(f),
37			Self::Media(selector, style) => write!(f, "@media {}{{{}}}", selector, style),
38			Self::FontFace(x) => x.fmt(f),
39		}
40	}
41}
42
43#[derive(Debug, PartialEq, Eq, Hash, Clone)]
44pub struct StyleRule(pub selector::Selector, pub Vec<Property>);
45
46impl std::fmt::Display for StyleRule {
47	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48		self.0.fmt(f)?;
49		"{".fmt(f)?;
50		for property in &self.1 {
51			property.fmt(f)?;
52		}
53		"}".fmt(f)
54	}
55}
56
57#[derive(Debug, PartialEq, Eq, Hash, Clone)]
58pub struct Style(pub Vec<Rule>);
59
60impl std::fmt::Display for Style {
61	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62		for rule in &self.0 {
63			rule.fmt(f)?;
64		}
65		Ok(())
66	}
67}
68
69impl Style {
70	pub fn append(&mut self, other: &mut Style) { self.0.append(&mut other.0); }
71}
72
73impl std::ops::Add for Style {
74	type Output = Self;
75
76	fn add(mut self, mut rhs: Self) -> Self {
77		self.0.append(&mut rhs.0);
78		self
79	}
80}
81
82#[macro_export]
83macro_rules! properties {
84	($($e:expr),*$(,)?) => {{
85		let mut v = Vec::new();
86		$($crate::AppendProperty::append_property($e, &mut v);)*
87		v
88	}};
89}
90
91#[macro_export]
92macro_rules! class {
93	($($rules:tt)*) => {$crate::style!(.& { $($rules)* })};
94}
95
96// TODO: procmacroify?
97#[macro_export]
98macro_rules! rule {
99	(@font-face { $($prop:ident : $value:expr),*$(,)? }) => {{
100		use $crate::font_face::*;
101
102		$crate::Rule::FontFace($crate::font_face::FontFace {
103			$($prop: $value),*,
104			..$crate::font_face::FontFace::default()
105		})
106	}};
107
108	// finished @media
109	((@media $($selector:tt)+) { $($style:tt)* }) => {
110		$crate::Rule::Media(
111			$crate::macros::media_selector!($($selector)+),
112			$crate::style!($($style)*),
113		)
114	};
115
116	// finished
117	(($($selector:tt)+) { $($rules:tt)* }) => {
118		$crate::Rule::Style($crate::StyleRule(
119			$crate::macros::selector!($($selector)+),
120			$crate::properties!($($rules)*),
121		))
122	};
123
124	// middle
125	(($($head:tt)+) $cur:tt $($tail:tt)*) => {
126		$crate::rule!(($($head)+ $cur) $($tail)*)
127	};
128
129	// start
130	($head:tt $($tail:tt)*) => {
131		$crate::rule!(($head) $($tail)*)
132	};
133}
134
135// TODO: procmacroify
136#[macro_export]
137#[doc(hidden)]
138macro_rules! __accumulate_style {
139	(
140		acc = $acc:expr,
141		rules = ()
142	) => {{
143		$crate::Style($acc)
144	}};
145
146	(
147		acc = $acc:expr,
148		rules = ([$($rule:tt)+] $($rest:tt)*)
149	) => {{
150		$acc.push($crate::rule!($($rule)+));
151		$crate::__accumulate_style!(acc = $acc, rules = ($($rest)*))
152	}};
153}
154
155// TODO: procmacroify
156#[macro_export]
157#[doc(hidden)]
158macro_rules! __style {
159	(
160		rules = ($($rules:tt)*),
161		new_rule = (),
162		rest = (),
163	) => {{
164		let mut acc = Vec::new();
165		$crate::__accumulate_style!(acc = acc, rules = ($($rules)*))
166	}};
167
168	(
169		rules = ($($rules:tt)*),
170		new_rule = ($($new_rule:tt)*),
171		rest = ({ $($decls:tt)* }),
172	) => {
173		$crate::__style!{
174			rules = ($($rules)* [$($new_rule)* { $($decls)* }]),
175			new_rule = (),
176			rest = (),
177		}
178	};
179
180	(
181		rules = ($($rules:tt)*),
182		new_rule = ($($new_rule:tt)*),
183		rest = ({ $($decls:tt)* } $($rest:tt)*),
184	) => {
185		$crate::__style!{
186			rules = ($($rules)* [$($new_rule)* { $($decls)* }]),
187			new_rule = (),
188			rest = ($($rest)*),
189		}
190	};
191
192	(
193		rules = ($($rules:tt)*),
194		new_rule = ($($new_rule:tt)*),
195		rest = ($cur:tt $($rest:tt)*),
196	) => {
197		$crate::__style!{
198			rules = ($($rules)*),
199			new_rule = ($($new_rule)* $cur),
200			rest = ($($rest)*),
201		}
202	};
203}
204
205#[macro_export]
206macro_rules! style {
207	($($tt:tt)+) => {
208		$crate::__style! {
209			rules = (),
210			new_rule = (),
211			rest = ($($tt)+),
212		}
213	};
214}
215
216// #[test]
217// fn macros() {
218//     assert_eq!(format!("#{:x}{:x}{:x}{:x}", 0xf1, 0xf2, 0xf3, 0xff), "#f1f2f3ff");
219//     assert_eq!(format!("#{:x}{:x}{:x}{:x}", 0xf1, 0xf2, 0xf3, 0x44), "#f1f2f344");
220//     assert_eq!(format!("#{:x}{:x}{:x}{:x}", 255, 128, 255, 255), "#ff80ffff");
221//     assert_eq!(format!("#{:x}{:x}{:x}{:x}", 255, 128, 255, 128), "#ff80ff80");
222
223//     assert_eq!(
224//         style! {
225//             div#("id"):first_child > span >> div::after {
226//                 margin_left!(10 px),
227//                 display!(block),
228//             }
229
230//             div.("fsdg"):hover > span >> div::after {
231//                 display!(block),
232//                 margin_left!(10 px),
233//             }
234//         }
235//         .to_string(),
236//         "div#id:first-child>span div::after{margin-left:10px;display:block;}div.fsdg:hover>span \
237//         div::after{display:block;margin-left:10px;}",
238//     );
239//     assert_eq!(
240//         style! {
241//             div#("id"):first_child > span >> div::after {
242//                 margin_left!(10 px),
243//                 display!(block),
244//             }
245//         }
246//         .to_string(),
247//         "div#id:first-child>span div::after{margin-left:10px;display:block;}",
248//     );
249//     assert_eq!(
250//         style! {
251//             div.&#("id"):first_child > span >> div::after {
252//                 margin_left!(10 px),
253//                 display!(block),
254//             }
255
256//             .&.("fsdg"):hover > span >> div::after {
257//                 display!(block),
258//                 margin_left!(10 px),
259//             }
260//         }
261//         .to_string(),
262//         "div.&#id:first-child>span div::after{margin-left:10px;display:block;}.&.fsdg:hover>span \
263//         div::after{display:block;margin-left:10px;}",
264//     );
265
266//     assert_eq!(
267//         style! {
268//             div.&#("id"):first_child > span >> div::after {
269//                 margin_left!(10 px),
270//                 display!(block),
271//             }
272
273//             .&.("fsdg"):hover > span >> div::after {
274//                 display!(block),
275//                 margin_left!(10 px),
276//             }
277
278//             .&.("asdf"):hover > span >> div::after {
279//                 display!(flex),
280//                 margin_right!(10 px),
281//             }
282//         }
283//         .to_string(),
284//         "div.&#id:first-child>span div::after{margin-left:10px;display:block;}.&.fsdg:hover>span \
285//         div::after{display:block;margin-left:10px;}.&.asdf:hover>span div::after{display:flex;margin-right:10px;}",
286//     );
287
288//     assert_eq!(selector!(div).to_string(), "div");
289//     assert_eq!(selector!(div:nth_child(5)).to_string(), "div:nth-child(5)");
290//     assert_eq!(selector!(.("fsdg")).to_string(), ".fsdg");
291//     assert_eq!(selector!(#("fsdg")).to_string(), "#fsdg");
292//     assert_eq!(selector!(:active).to_string(), ":active");
293//     assert_eq!(selector!(::after).to_string(), "::after");
294//     assert_eq!(selector!(div.("fsdg"):hover > span >> div::after).to_string(), "div.fsdg:hover>span div::after");
295//     assert_eq!(selector!(div#("id"):first_child > span >> div::after).to_string(), "div#id:first-child>span div::after");
296
297//     assert_eq!(
298//         rule! {
299//             div.("fsdg"):hover > span >> div::after {
300//                 display!(block),
301//                 margin_left!(10 px),
302//             }
303//         }
304//         .to_string(),
305//         "div.fsdg:hover>span div::after{display:block;margin-left:10px;}",
306//     );
307// }