Skip to main content

pfx_live/flat/
viewport.rs

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}