1use pfx_input::{Control, Glyph, GlyphStyle};
2use pfx_live::flat::effects::Effects;
3use pfx_live::flat::look::{Blend, Look, LookFrame, TokenSet};
4use pfx_live::flat::{Draw, Group, Light, Matrix, ShadowCurve, Srgba};
5
6#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct UiFrame {
8 pub layout: [f32; 2],
9 pub safe: [f32; 4],
10 pub ui_scale: f32,
11 pub pixels_per_unit: f32,
12 pub window: [u32; 2],
13 pub window_units: [f32; 2],
14 pub content: [f32; 4],
15 pub backdrop: bool,
16 pub deck: bool,
17 pub glyph_style: GlyphStyle,
18 pub alpha: f32,
19 pub seconds: f32,
20}
21
22impl Default for UiFrame {
23 fn default() -> Self {
24 Self {
25 layout: [1920.0, 1080.0],
26 safe: [0.0, 0.0, 1920.0, 1080.0],
27 ui_scale: 1.0,
28 pixels_per_unit: 1.0,
29 window: [1920, 1080],
30 window_units: [1920.0, 1080.0],
31 content: [0.0, 0.0, 1920.0, 1080.0],
32 backdrop: false,
33 deck: false,
34 glyph_style: GlyphStyle::Standard,
35 alpha: 0.0,
36 seconds: 0.0,
37 }
38 }
39}
40
41#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
42pub enum Anchor {
43 #[default]
44 Start,
45 Center,
46 End,
47}
48
49#[derive(Clone, Debug, PartialEq)]
50pub struct Label {
51 pub text: String,
52 pub family: String,
53 pub size: f32,
54 pub weight: u16,
55 pub at: [f32; 2],
56 pub anchor: Anchor,
57 pub colour: Srgba,
58 pub elevation: f32,
59 pub id: u32,
60 pub line: Option<f32>,
61 pub wrap: Option<f32>,
62 pub height: Option<f32>,
63 pub vertical: Anchor,
64 pub transform: Option<Matrix>,
65 pub fallbacks: Vec<String>,
66}
67
68impl Label {
69 pub fn new(
70 text: impl Into<String>,
71 family: impl Into<String>,
72 size: f32,
73 at: [f32; 2],
74 ) -> Self {
75 Self {
76 text: text.into(),
77 family: family.into(),
78 size,
79 weight: 400,
80 at,
81 anchor: Anchor::Start,
82 colour: Srgba([1.0, 1.0, 1.0, 1.0]),
83 elevation: 0.0,
84 id: 0,
85 line: None,
86 wrap: None,
87 height: None,
88 vertical: Anchor::Start,
89 transform: None,
90 fallbacks: Vec::new(),
91 }
92 }
93
94 pub fn line(mut self, height: f32) -> Self {
95 self.line = Some(height);
96 self
97 }
98
99 pub fn line_height(&self) -> f32 {
100 self.line.unwrap_or(self.size * 1.25)
101 }
102
103 pub fn wrap(mut self, width: f32) -> Self {
104 self.wrap = Some(width);
105 self
106 }
107
108 pub fn boxed(mut self, size: [f32; 2], horizontal: Anchor, vertical: Anchor) -> Self {
109 self.wrap = Some(size[0]);
110 self.height = Some(size[1]);
111 self.anchor = horizontal;
112 self.vertical = vertical;
113 self
114 }
115
116 pub fn transform(mut self, transform: Matrix) -> Self {
117 self.transform = Some(transform);
118 self
119 }
120
121 pub fn fallbacks<S: Into<String>>(mut self, families: impl IntoIterator<Item = S>) -> Self {
122 self.fallbacks = families.into_iter().map(Into::into).collect();
123 self
124 }
125
126 pub fn weight(mut self, weight: u16) -> Self {
127 self.weight = weight;
128 self
129 }
130
131 pub fn anchor(mut self, anchor: Anchor) -> Self {
132 self.anchor = anchor;
133 self
134 }
135
136 pub fn colour(mut self, colour: Srgba) -> Self {
137 self.colour = colour;
138 self
139 }
140
141 pub fn elevation(mut self, elevation: f32) -> Self {
142 self.elevation = elevation;
143 self
144 }
145
146 pub fn id(mut self, id: u32) -> Self {
147 self.id = id;
148 self
149 }
150}
151
152#[derive(Clone, Debug, PartialEq)]
153pub enum PromptOf {
154 Action(String),
155 Control(Control),
156 Glyph(Glyph),
157}
158
159#[derive(Clone, Debug, PartialEq)]
160pub struct Prompt {
161 pub of: PromptOf,
162 pub at: [f32; 2],
163 pub height: f32,
164 pub gap: f32,
165 pub colour: Srgba,
166 pub elevation: f32,
167 pub family: String,
168 pub weight: u16,
169 pub floor: f32,
170 pub id: u32,
171}
172
173impl Prompt {
174 pub fn new(of: PromptOf, at: [f32; 2], height: f32) -> Self {
175 Self {
176 of,
177 at,
178 height,
179 gap: height * 0.2,
180 colour: Srgba([1.0, 1.0, 1.0, 1.0]),
181 elevation: 0.0,
182 family: String::new(),
183 weight: 600,
184 floor: 0.0,
185 id: 0,
186 }
187 }
188
189 pub fn floor(mut self, floor: f32) -> Self {
190 self.floor = floor;
191 self
192 }
193
194 pub fn action(name: impl Into<String>, at: [f32; 2], height: f32) -> Self {
195 Self::new(PromptOf::Action(name.into()), at, height)
196 }
197
198 pub fn control(control: Control, at: [f32; 2], height: f32) -> Self {
199 Self::new(PromptOf::Control(control), at, height)
200 }
201
202 pub fn glyph(glyph: Glyph, at: [f32; 2], height: f32) -> Self {
203 Self::new(PromptOf::Glyph(glyph), at, height)
204 }
205
206 pub fn colour(mut self, colour: Srgba) -> Self {
207 self.colour = colour;
208 self
209 }
210
211 pub fn elevation(mut self, elevation: f32) -> Self {
212 self.elevation = elevation;
213 self
214 }
215
216 pub fn font(mut self, family: impl Into<String>, weight: u16) -> Self {
217 self.family = family.into();
218 self.weight = weight;
219 self
220 }
221
222 pub fn gap(mut self, gap: f32) -> Self {
223 self.gap = gap;
224 self
225 }
226
227 pub fn id(mut self, id: u32) -> Self {
228 self.id = id;
229 self
230 }
231}
232
233#[derive(Clone, Debug, PartialEq)]
234pub struct UiLook {
235 pub from: Look,
236 pub to: Look,
237 pub progress: f32,
238 pub blend: Blend,
239 pub base: TokenSet,
240}
241
242impl UiLook {
243 pub fn still(look: Look, base: TokenSet) -> Self {
244 Self {
245 from: look.clone(),
246 to: look,
247 progress: 1.0,
248 blend: Blend::Crossfade,
249 base,
250 }
251 }
252
253 pub fn of(frame: &LookFrame<'_>) -> Self {
254 Self {
255 from: frame.from.clone(),
256 to: frame.to.clone(),
257 progress: frame.progress,
258 blend: frame.blend,
259 base: frame.base.clone(),
260 }
261 }
262
263 pub fn frame(&self) -> LookFrame<'_> {
264 LookFrame {
265 from: &self.from,
266 to: &self.to,
267 progress: self.progress,
268 blend: self.blend,
269 base: &self.base,
270 }
271 }
272}
273
274#[derive(Clone, Debug, Default, PartialEq)]
275pub struct Ui {
276 pub backdrop: Vec<Draw>,
277 pub draws: Vec<Draw>,
278 pub labels: Vec<Label>,
279 pub prompts: Vec<Prompt>,
280 pub groups: Vec<Group>,
281 pub curve: Option<ShadowCurve>,
282 pub light: Light,
283 pub clear: Option<Srgba>,
284 pub look: Option<UiLook>,
285}
286
287impl Ui {
288 pub fn new() -> Self {
289 Self::default()
290 }
291
292 pub fn is_empty(&self) -> bool {
293 self.backdrop.is_empty()
294 && self.draws.is_empty()
295 && self.labels.is_empty()
296 && self.prompts.is_empty()
297 }
298
299 pub fn backdrop(&mut self, draw: Draw) -> &mut Self {
300 self.backdrop.push(draw);
301 self
302 }
303
304 pub fn draw(&mut self, draw: Draw) -> &mut Self {
305 self.draws.push(draw);
306 self
307 }
308
309 pub fn label(&mut self, label: Label) -> &mut Self {
310 self.labels.push(label);
311 self
312 }
313
314 pub fn prompt(&mut self, prompt: Prompt) -> &mut Self {
315 self.prompts.push(prompt);
316 self
317 }
318
319 pub fn clear(&mut self, colour: Srgba) -> &mut Self {
320 self.clear = Some(colour);
321 self
322 }
323
324 pub fn look(&mut self, frame: &LookFrame<'_>) -> &mut Self {
325 self.look = Some(UiLook::of(frame));
326 self
327 }
328
329 pub fn effects(&mut self, effects: &mut Effects) -> &mut Self {
330 let (draws, light) = effects.compose(&self.draws, self.light);
331 self.draws = draws.to_vec();
332 self.light = light;
333 self
334 }
335
336 pub fn group(&mut self, opacity: f32) -> u16 {
337 self.groups.push(Group { opacity });
338 (self.groups.len() - 1) as u16
339 }
340}