1use nifty_runtime::{
5 Align, Backdrop, ButtonStyle, Icon, Justify, Surface, TextAlign, TextColor, TextStyle,
6};
7use nifty_syntax::ast::{self, ExprKind as Ast, StringPart, UnaryOp};
8
9use super::Checker;
10use crate::elements::Options;
11
12pub(super) const ALIGNMENTS: &[(&str, Align)] = &[
13 ("stretch", Align::Stretch),
14 ("start", Align::Start),
15 ("center", Align::Center),
16 ("end", Align::End),
17];
18
19pub(super) const JUSTIFICATIONS: &[(&str, Justify)] = &[
20 ("start", Justify::Start),
21 ("center", Justify::Center),
22 ("end", Justify::End),
23 ("space_between", Justify::SpaceBetween),
24];
25
26pub(super) const SURFACES: &[(&str, Surface)] = &[
27 ("none", Surface::None),
28 ("sidebar", Surface::Sidebar),
29 ("content", Surface::Content),
30 ("card", Surface::Card),
31];
32
33pub(super) const BACKDROPS: &[(&str, Backdrop)] = &[
34 ("standard", Backdrop::Standard),
35 ("translucent", Backdrop::Translucent),
36 ("solid", Backdrop::Solid),
37];
38
39pub(super) const TEXT_STYLES: &[(&str, TextStyle)] = &[
40 ("body", TextStyle::Body),
41 ("title", TextStyle::Title),
42 ("headline", TextStyle::Headline),
43 ("caption", TextStyle::Caption),
44 ("large_title", TextStyle::LargeTitle),
45];
46
47pub(super) const TEXT_COLORS: &[(&str, TextColor)] = &[
48 ("primary", TextColor::Primary),
49 ("secondary", TextColor::Secondary),
50 ("tertiary", TextColor::Tertiary),
51 ("accent", TextColor::Accent),
52 ("success", TextColor::Success),
53 ("warning", TextColor::Warning),
54 ("danger", TextColor::Danger),
55];
56
57pub(super) const TEXT_ALIGNMENTS: &[(&str, TextAlign)] = &[
58 ("start", TextAlign::Start),
59 ("center", TextAlign::Center),
60 ("end", TextAlign::End),
61];
62
63pub(super) const BUTTON_STYLES: &[(&str, ButtonStyle)] = &[
64 ("normal", ButtonStyle::Normal),
65 ("primary", ButtonStyle::Primary),
66 ("plain", ButtonStyle::Plain),
67 ("icon", ButtonStyle::Icon),
68];
69
70pub(super) const ICONS: &[(&str, Icon)] = &[
72 ("plus", Icon::Plus),
73 ("trash", Icon::Trash),
74 ("star", Icon::Star),
75 ("star_fill", Icon::StarFill),
76 ("check", Icon::Check),
77 ("check_circle", Icon::CheckCircle),
78 ("circle", Icon::Circle),
79 ("home", Icon::Home),
80 ("inbox", Icon::Inbox),
81 ("archive", Icon::Archive),
82 ("list", Icon::List),
83 ("calendar", Icon::Calendar),
84 ("sun", Icon::Sun),
85 ("flag", Icon::Flag),
86 ("close", Icon::Close),
87 ("gear", Icon::Gear),
88 ("search", Icon::Search),
89 ("mail", Icon::Mail),
90 ("send", Icon::Send),
91 ("folder", Icon::Folder),
92 ("clock", Icon::Clock),
93 ("tag", Icon::Tag),
94 ("person", Icon::Person),
95 ("document", Icon::Document),
96 ("download", Icon::Download),
97 ("heart", Icon::Heart),
98 ("bell", Icon::Bell),
99 ("pencil", Icon::Pencil),
100 ("share", Icon::Share),
101 ("info", Icon::Info),
102];
103
104impl Options {
105 #[must_use]
107 pub fn names(self) -> Vec<&'static str> {
108 match self {
109 Self::Alignment => names(ALIGNMENTS),
110 Self::Justification => names(JUSTIFICATIONS),
111 Self::TextStyle => names(TEXT_STYLES),
112 Self::TextColor => names(TEXT_COLORS),
113 Self::TextAlignment => names(TEXT_ALIGNMENTS),
114 Self::ButtonStyle => names(BUTTON_STYLES),
115 Self::Surface => names(SURFACES),
116 Self::Icon => names(ICONS),
117 Self::Backdrop => names(BACKDROPS),
118 }
119 }
120}
121
122fn names<T>(options: &[(&'static str, T)]) -> Vec<&'static str> {
123 options.iter().map(|&(name, _)| name).collect()
124}
125
126const MAX_POINTS: f64 = 10_000.0;
128pub(super) const MAX_FIXED_NUMBER: i64 = 1_000_000_000;
131
132impl Checker {
133 pub(super) fn const_points(&mut self, expr: &ast::Expr) -> Option<f32> {
135 let value = match expr.kind {
136 Ast::Int(value) => u16::try_from(value).ok().map(f64::from),
137 Ast::Float(value) => Some(value),
138 _ => {
139 self.error("this must be a number such as `12`", expr.span);
140 return None;
141 }
142 };
143 if let Some(value) = value.filter(|value| (0.0..=MAX_POINTS).contains(value)) {
144 return Some(points(value));
145 }
146 self.error(
147 format!("this must be between 0 and {MAX_POINTS}"),
148 expr.span,
149 );
150 None
151 }
152
153 pub(super) fn const_number(&mut self, expr: &ast::Expr) -> Option<i64> {
155 let value = match &expr.kind {
156 Ast::Int(value) => Some(*value),
157 Ast::Unary {
158 op: UnaryOp::Neg,
159 operand,
160 } => match operand.kind {
161 Ast::Int(value) => Some(-value),
162 _ => None,
163 },
164 _ => None,
165 };
166 let Some(value) = value else {
167 self.error("this must be a whole number such as `10`", expr.span);
168 return None;
169 };
170 if (-MAX_FIXED_NUMBER..=MAX_FIXED_NUMBER).contains(&value) {
171 return Some(value);
172 }
173 self.error(
174 format!("this must be between -{MAX_FIXED_NUMBER} and {MAX_FIXED_NUMBER}"),
175 expr.span,
176 );
177 None
178 }
179
180 pub(super) fn const_string(&mut self, expr: &ast::Expr) -> Option<String> {
182 if let Ast::String(parts) = &expr.kind {
183 let text: Option<String> = parts
184 .iter()
185 .map(|part| match part {
186 StringPart::Text(text) => Some(text.as_str()),
187 StringPart::Expr(_) => None,
188 })
189 .collect();
190 if text.is_some() {
191 return text;
192 }
193 }
194 self.error("this must be fixed text such as \"Title\"", expr.span);
195 None
196 }
197
198 pub(super) fn const_bool(&mut self, expr: &ast::Expr) -> Option<bool> {
200 if let Ast::Bool(value) = expr.kind {
201 return Some(value);
202 }
203 self.error("this must be `true` or `false`", expr.span);
204 None
205 }
206
207 pub(super) fn const_member<T: Copy>(
209 &mut self,
210 expr: &ast::Expr,
211 options: &[(&str, T)],
212 ) -> Option<T> {
213 if let Ast::ImplicitMember(name) = &expr.kind
214 && let Some((_, value)) = options.iter().find(|(option, _)| *option == name.name)
215 {
216 return Some(*value);
217 }
218 let names: Vec<String> = options
219 .iter()
220 .map(|(name, _)| format!("`.{name}`"))
221 .collect();
222 self.error(format!("expected one of {}", names.join(", ")), expr.span);
223 None
224 }
225}
226
227#[expect(
228 clippy::cast_possible_truncation,
229 reason = "range-checked to at most MAX_POINTS, where f32 is far finer than a point"
230)]
231fn points(value: f64) -> f32 {
232 value as f32
233}