1use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
2
3use crate::{length_num::LengthNum, query::MediaQueryStatus};
4
5#[cfg(debug_assertions)]
6use float_pigment_css_macro::CompatibilityEnumCheck;
7
8#[derive(Debug, Clone)]
9pub(crate) struct Media {
10 pub(crate) parent: Option<Rc<Media>>,
11 pub(crate) media_queries: Vec<MediaQuery>,
12}
13
14#[derive(Debug, Clone)]
15pub(crate) struct MediaQuery {
16 pub(crate) decorator: MediaTypeDecorator,
17 pub(crate) cond: Vec<MediaExpression>,
18}
19
20#[repr(C)]
21#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
22#[cfg_attr(debug_assertions, derive(CompatibilityEnumCheck))]
23pub(crate) enum MediaExpression {
24 Unknown,
25 MediaType(MediaType),
26 Orientation(Orientation),
27 Width(f32),
28 MinWidth(f32),
29 MaxWidth(f32),
30 Height(f32),
31 MinHeight(f32),
32 MaxHeight(f32),
33 Theme(Theme),
34}
35
36#[repr(C)]
37#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
38#[cfg_attr(debug_assertions, derive(CompatibilityEnumCheck))]
39pub(crate) enum MediaTypeDecorator {
40 None,
41 Not,
42 Only,
43}
44
45#[repr(C)]
46#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
47#[cfg_attr(debug_assertions, derive(CompatibilityEnumCheck))]
48pub(crate) enum MediaType {
49 None,
50 All,
51 Screen,
52}
53
54#[repr(C)]
55#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
56#[cfg_attr(debug_assertions, derive(CompatibilityEnumCheck))]
57pub(crate) enum Orientation {
58 None,
59 Portrait,
60 Landscape,
61}
62
63#[repr(C)]
65#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
66#[cfg_attr(debug_assertions, derive(CompatibilityEnumCheck))]
67pub enum Theme {
68 None,
70 Light,
72 Dark,
74}
75
76impl Media {
77 pub(crate) fn new(parent: Option<Rc<Media>>) -> Self {
78 Self {
79 parent,
80 media_queries: vec![],
81 }
82 }
83
84 pub(crate) fn add_media_query(&mut self, mq: MediaQuery) {
85 self.media_queries.push(mq)
86 }
87
88 pub(crate) fn is_valid<L: LengthNum>(&self, mqs: &MediaQueryStatus<L>) -> bool {
89 if let Some(parent) = &self.parent {
90 if !parent.is_valid(mqs) {
91 return false;
92 }
93 }
94 for mq in self.media_queries.iter() {
95 if mq.is_valid(mqs) {
96 return true;
97 }
98 }
99 false
100 }
101
102 pub(crate) fn to_media_query_string_list(&self, list: &mut Vec<String>) {
103 if let Some(p) = &self.parent {
104 p.to_media_query_string_list(list);
105 }
106 list.push(
107 self.media_queries
108 .iter()
109 .map(|x| x.to_media_query_string())
110 .collect::<Box<[String]>>()
111 .join(", "),
112 )
113 }
114}
115
116impl MediaQuery {
117 pub(crate) fn new() -> Self {
118 Self {
119 decorator: MediaTypeDecorator::None,
120 cond: vec![],
121 }
122 }
123
124 pub(crate) fn set_decorator(&mut self, d: MediaTypeDecorator) {
125 self.decorator = d;
126 }
127
128 pub(crate) fn add_media_expression(&mut self, mq: MediaExpression) {
129 self.cond.push(mq)
130 }
131
132 fn is_valid<L: LengthNum>(&self, mqs: &MediaQueryStatus<L>) -> bool {
133 let allow_unknown = self.decorator != MediaTypeDecorator::Only;
134 for cond in self.cond.iter() {
135 let mut matched = match cond {
136 MediaExpression::Unknown => allow_unknown,
137 MediaExpression::MediaType(mt) => match mt {
138 MediaType::None => false,
139 MediaType::All => true,
140 MediaType::Screen => mqs.is_screen,
141 },
142 MediaExpression::Orientation(o) => match o {
143 Orientation::None => false,
144 Orientation::Portrait => mqs.width <= mqs.height,
145 Orientation::Landscape => mqs.width > mqs.height,
146 },
147 MediaExpression::Width(x) => mqs.width.to_f32() == *x,
148 MediaExpression::MinWidth(x) => mqs.width.to_f32() >= *x,
149 MediaExpression::MaxWidth(x) => mqs.width.to_f32() <= *x,
150 MediaExpression::Height(x) => mqs.height.to_f32() == *x,
151 MediaExpression::MinHeight(x) => mqs.height.to_f32() >= *x,
152 MediaExpression::MaxHeight(x) => mqs.height.to_f32() <= *x,
153 MediaExpression::Theme(t) => match t {
154 Theme::None => false,
155 Theme::Light => mqs.theme == Theme::Light,
156 Theme::Dark => mqs.theme == Theme::Dark,
157 },
158 };
159 if self.decorator == MediaTypeDecorator::Not {
160 matched = !matched;
161 }
162 if !matched {
163 return false;
164 }
165 }
166 true
167 }
168
169 pub(crate) fn to_media_query_string(&self) -> String {
170 let decorator = match self.decorator {
171 MediaTypeDecorator::None => "",
172 MediaTypeDecorator::Not => "not ",
173 MediaTypeDecorator::Only => "only ",
174 };
175 let cond = self
176 .cond
177 .iter()
178 .map(|cond| match cond {
179 MediaExpression::Unknown => "unknown".into(),
180 MediaExpression::MediaType(mt) => match mt {
181 MediaType::None => "none".into(),
182 MediaType::All => "all".into(),
183 MediaType::Screen => "screen".into(),
184 },
185 MediaExpression::Orientation(o) => match o {
186 Orientation::None => "(orientation: none)",
187 Orientation::Portrait => "(orientation: portrait)",
188 Orientation::Landscape => "(orientation: landscape)",
189 }
190 .into(),
191 MediaExpression::Width(x) => format!("(width: {}px)", x),
192 MediaExpression::MinWidth(x) => format!("(min-width: {}px)", x),
193 MediaExpression::MaxWidth(x) => format!("(max-width: {}px)", x),
194 MediaExpression::Height(x) => format!("(height: {}px)", x),
195 MediaExpression::MinHeight(x) => format!("(min-height: {}px)", x),
196 MediaExpression::MaxHeight(x) => format!("(max-height: {}px)", x),
197 MediaExpression::Theme(t) => match t {
198 Theme::None => "(prefers-color-scheme: none)",
199 Theme::Light => "(prefers-color-scheme: light)",
200 Theme::Dark => "(prefers-color-scheme: dark)",
201 }
202 .into(),
203 })
204 .collect::<Box<[String]>>()
205 .join(" and ");
206 format!("{}{}", decorator, cond)
207 }
208}