Skip to main content

pfx_play/
ui.rs

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}