1use super::{Draw, IDENTITY, Matrix, Shadow, Shape, Srgba, Stroke, multiply, sdf, translation};
2use pfx_core::ease::Ease;
3use std::f32::consts::FRAC_PI_2;
4
5#[derive(Clone, Copy, Debug, PartialEq)]
6pub struct Frame {
7 pub size: [f32; 2],
8 pub bezel: f32,
9 pub radius: f32,
10 pub colour: Srgba,
11}
12
13impl Frame {
14 pub fn outer(&self, orientation: Orientation) -> [f32; 2] {
15 let [a, b] = self.size;
16 let (short, long) = (a.min(b), a.max(b));
17 match orientation {
18 Orientation::Portrait => [short, long],
19 Orientation::Landscape => [long, short],
20 }
21 }
22
23 pub fn screen(&self, orientation: Orientation) -> [f32; 2] {
24 let [w, h] = self.outer(orientation);
25 let bezel = self.bezel.max(0.0);
26 [(w - 2.0 * bezel).max(0.0), (h - 2.0 * bezel).max(0.0)]
27 }
28
29 pub fn screen_radius(&self) -> f32 {
30 (self.radius - self.bezel.max(0.0)).max(0.0)
31 }
32}
33
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
35pub enum Orientation {
36 #[default]
37 Portrait,
38 Landscape,
39}
40
41impl Orientation {
42 pub fn other(self) -> Self {
43 match self {
44 Self::Portrait => Self::Landscape,
45 Self::Landscape => Self::Portrait,
46 }
47 }
48}
49
50#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
51pub enum Mirror {
52 #[default]
53 None,
54 Vertical,
55 Horizontal,
56}
57
58impl Mirror {
59 pub fn signs(self) -> [f32; 2] {
60 match self {
61 Self::None => [1.0, 1.0],
62 Self::Vertical => [1.0, -1.0],
63 Self::Horizontal => [-1.0, 1.0],
64 }
65 }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq)]
69pub struct Turn {
70 pub to: Orientation,
71 pub start: f32,
72 pub duration: f32,
73 pub ease: Ease,
74 pub clockwise: bool,
75}
76
77impl Turn {
78 pub fn progress(&self, now: f32) -> f32 {
79 if self.duration.is_nan() || self.duration <= 0.0 || !self.duration.is_finite() {
80 return 1.0;
81 }
82 ((now - self.start) / self.duration).clamp(0.0, 1.0)
83 }
84}
85
86#[derive(Clone, Copy, Debug, PartialEq)]
87pub struct Viewport {
88 pub centre: [f32; 2],
89 pub frame: Frame,
90 pub orientation: Orientation,
91 pub turn: Option<Turn>,
92 pub mirror: Mirror,
93}
94
95impl Viewport {
96 pub fn new(centre: [f32; 2], frame: Frame) -> Self {
97 Self {
98 centre,
99 frame,
100 orientation: Orientation::Portrait,
101 turn: None,
102 mirror: Mirror::None,
103 }
104 }
105
106 pub fn orientation(mut self, orientation: Orientation) -> Self {
107 self.orientation = orientation;
108 self
109 }
110
111 pub fn mirror(mut self, mirror: Mirror) -> Self {
112 self.mirror = mirror;
113 self
114 }
115
116 pub fn turn_to(
117 &mut self,
118 to: Orientation,
119 now: f32,
120 duration: f32,
121 ease: Ease,
122 clockwise: bool,
123 reduced_motion: bool,
124 ) {
125 self.orientation = self.at(now).orientation;
126 self.turn = (to != self.orientation).then_some(Turn {
127 to,
128 start: now,
129 duration: if reduced_motion { 0.0 } else { duration },
130 ease,
131 clockwise,
132 });
133 }
134
135 pub fn settle(&mut self, now: f32) {
136 if let Some(turn) = self.turn
137 && turn.progress(now) >= 1.0
138 {
139 self.orientation = turn.to;
140 self.turn = None;
141 }
142 }
143
144 pub fn at(&self, now: f32) -> Placement {
145 let (orientation, angle, progress) = match self.turn {
146 Some(turn) if turn.progress(now) < 1.0 => {
147 let progress = turn.progress(now);
148 let sign = if turn.clockwise { 1.0 } else { -1.0 };
149 (
150 self.orientation,
151 sign * FRAC_PI_2 * turn.ease.at(progress),
152 progress,
153 )
154 }
155 Some(turn) => (turn.to, 0.0, 1.0),
156 None => (self.orientation, 0.0, 1.0),
157 };
158 Placement {
159 layout: self.frame.screen(orientation),
160 orientation,
161 angle,
162 progress,
163 centre: self.centre,
164 frame: self.frame,
165 mirror: self.mirror,
166 }
167 }
168}
169
170#[derive(Clone, Copy, Debug, PartialEq)]
171pub struct Placement {
172 pub layout: [f32; 2],
173 pub orientation: Orientation,
174 pub angle: f32,
175 pub progress: f32,
176 pub centre: [f32; 2],
177 pub frame: Frame,
178 pub mirror: Mirror,
179}
180
181impl Placement {
182 pub fn settled(&self) -> bool {
183 self.progress >= 1.0
184 }
185
186 fn turned(&self) -> [[f32; 2]; 2] {
187 let (s, c) = self.angle.sin_cos();
188 [[c, s], [-s, c]]
189 }
190
191 fn linear(&self) -> [[f32; 2]; 2] {
192 let r = self.turned();
193 let [fx, fy] = self.mirror.signs();
194 [[r[0][0] * fx, r[0][1] * fx], [r[1][0] * fy, r[1][1] * fy]]
195 }
196
197 pub fn to_window(&self, point: [f32; 2]) -> [f32; 2] {
198 let m = self.linear();
199 let p = [
200 point[0] - self.layout[0] * 0.5,
201 point[1] - self.layout[1] * 0.5,
202 ];
203 [
204 self.centre[0] + m[0][0] * p[0] + m[1][0] * p[1],
205 self.centre[1] + m[0][1] * p[0] + m[1][1] * p[1],
206 ]
207 }
208
209 pub fn to_layout(&self, window: [f32; 2]) -> [f32; 2] {
210 let m = self.linear();
211 let d = [window[0] - self.centre[0], window[1] - self.centre[1]];
212 [
213 self.layout[0] * 0.5 + m[0][0] * d[0] + m[0][1] * d[1],
214 self.layout[1] * 0.5 + m[1][0] * d[0] + m[1][1] * d[1],
215 ]
216 }
217
218 pub fn pointer(&self, window: [f32; 2]) -> Option<[f32; 2]> {
219 let point = self.to_layout(window);
220 let half = [self.layout[0] * 0.5, self.layout[1] * 0.5];
221 let radius = self.frame.screen_radius().min(half[0]).min(half[1]);
222 let inside =
223 sdf::rect([point[0] - half[0], point[1] - half[1]], half, [radius; 4])[0] <= 0.0;
224 inside.then_some(point)
225 }
226
227 pub fn gravity(&self) -> [f32; 2] {
228 let m = self.linear();
229 let g = [m[0][1], m[1][1]];
230 let length = (g[0] * g[0] + g[1] * g[1]).sqrt();
231 [g[0] / length, g[1] / length]
232 }
233
234 fn window_from_layout(&self) -> Matrix {
235 let m = self.linear();
236 let mut out = IDENTITY;
237 out[0][0] = m[0][0];
238 out[0][1] = m[0][1];
239 out[1][0] = m[1][0];
240 out[1][1] = m[1][1];
241 multiply(
242 translation(self.centre[0], self.centre[1]),
243 multiply(
244 out,
245 translation(-self.layout[0] * 0.5, -self.layout[1] * 0.5),
246 ),
247 )
248 }
249
250 pub fn reflected(&self, transform: Matrix) -> Matrix {
251 multiply(self.window_from_layout(), transform)
252 }
253
254 pub fn transform(&self, transform: Matrix) -> Matrix {
255 let [fx, fy] = self.mirror.signs();
256 let mut flip = IDENTITY;
257 flip[0][0] = fx;
258 flip[1][1] = fy;
259 multiply(self.reflected(transform), flip)
260 }
261
262 pub fn place(&self, draw: &Draw) -> Draw {
263 let mut out = *draw;
264 out.transform = self.transform(draw.transform);
265 out
266 }
267
268 pub fn place_all(&self, draws: &mut [Draw]) {
269 for draw in draws {
270 draw.transform = self.transform(draw.transform);
271 }
272 }
273
274 pub fn frame_transform(&self) -> Matrix {
275 multiply(
276 translation(self.centre[0], self.centre[1]),
277 super::rotation(self.angle),
278 )
279 }
280
281 pub fn screen_bounds(&self) -> [f32; 4] {
282 let mut out = [f32::MAX, f32::MAX, f32::MIN, f32::MIN];
283 for corner in [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]] {
284 let p = self.to_window([corner[0] * self.layout[0], corner[1] * self.layout[1]]);
285 out = [
286 out[0].min(p[0]),
287 out[1].min(p[1]),
288 out[2].max(p[0]),
289 out[3].max(p[1]),
290 ];
291 }
292 out
293 }
294
295 pub fn frame_draws(&self, elevation: f32, backdrop: Option<Srgba>, reach: f32) -> Vec<Draw> {
296 let screen = [self.layout[0] * 0.5, self.layout[1] * 0.5];
297 let bezel = self.frame.bezel.max(0.0);
298 let inner = self.frame.screen_radius().min(screen[0]).min(screen[1]);
299 let at = self.frame_transform();
300 let mut draws = Vec::with_capacity(2);
301 if let Some(backdrop) = backdrop
302 && reach > 0.0
303 {
304 let inset = (bezel * 0.5).min(1.0);
305 let edge = bezel - inset;
306 draws.push(
307 Draw::new(Shape::Rect {
308 half: [
309 screen[0] + edge + reach * 0.5,
310 screen[1] + edge + reach * 0.5,
311 ],
312 radii: [inner + edge + reach * 0.5; 4],
313 })
314 .transform(at)
315 .stroke(Stroke::solid(backdrop, reach))
316 .elevation(elevation)
317 .shadow(Shadow::None),
318 );
319 }
320 if bezel > 0.0 {
321 draws.push(
322 Draw::new(Shape::Rect {
323 half: [screen[0] + bezel * 0.5, screen[1] + bezel * 0.5],
324 radii: [inner + bezel * 0.5; 4],
325 })
326 .transform(at)
327 .stroke(Stroke::solid(self.frame.colour, bezel))
328 .elevation(elevation)
329 .shadow(Shadow::None),
330 );
331 }
332 draws
333 }
334}