Skip to main content

kozan_core/widget/
viewport.rs

1//! Viewport — the rendering surface dimensions and scale factor.
2//!
3//! Chrome equivalent: `ScreenInfo::device_scale_factor` + `VisualProperties`
4//! (`visible_viewport_size`). Updated by `FrameWidget` on resize and
5//! scale-factor-change. Read by layout for viewport units (vw, vh).
6
7/// The rendering viewport — physical dimensions and DPI scale factor.
8///
9/// Physical pixels = logical pixels × `scale_factor`.
10///
11/// Used by:
12/// - Layout engine: viewport units (`vw`, `vh`, `vmin`, `vmax`)
13/// - Style resolution: `ResolveContext` gets `viewport_width`/`viewport_height`
14/// - Media queries: `@media (min-width: ...)`
15#[derive(Debug, Clone, Copy)]
16pub struct Viewport {
17    /// Width in physical pixels.
18    width: u32,
19    /// Height in physical pixels.
20    height: u32,
21    /// DPI scale factor (1.0 = standard, 2.0 = Retina/HiDPI).
22    scale_factor: f64,
23}
24
25impl Viewport {
26    /// Create a viewport with the given dimensions and scale factor.
27    #[must_use]
28    pub fn new(width: u32, height: u32, scale_factor: f64) -> Self {
29        Self {
30            width,
31            height,
32            scale_factor,
33        }
34    }
35
36    /// Width in physical pixels.
37    #[inline]
38    #[must_use]
39    pub fn width(&self) -> u32 {
40        self.width
41    }
42
43    /// Height in physical pixels.
44    #[inline]
45    #[must_use]
46    pub fn height(&self) -> u32 {
47        self.height
48    }
49
50    /// DPI scale factor.
51    #[inline]
52    #[must_use]
53    pub fn scale_factor(&self) -> f64 {
54        self.scale_factor
55    }
56
57    /// Width in logical (CSS-like) pixels.
58    ///
59    /// This is what layout uses for viewport units.
60    #[inline]
61    #[must_use]
62    pub fn logical_width(&self) -> f64 {
63        self.width as f64 / self.scale_factor
64    }
65
66    /// Height in logical (CSS-like) pixels.
67    #[inline]
68    #[must_use]
69    pub fn logical_height(&self) -> f64 {
70        self.height as f64 / self.scale_factor
71    }
72
73    /// Update the physical dimensions.
74    pub fn resize(&mut self, width: u32, height: u32) {
75        self.width = width;
76        self.height = height;
77    }
78
79    /// Update the DPI scale factor.
80    pub fn set_scale_factor(&mut self, factor: f64) {
81        self.scale_factor = factor;
82    }
83}
84
85impl Default for Viewport {
86    fn default() -> Self {
87        Self {
88            width: 0,
89            height: 0,
90            scale_factor: 1.0,
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn default_viewport() {
101        let vp = Viewport::default();
102        assert_eq!(vp.width(), 0);
103        assert_eq!(vp.height(), 0);
104        assert_eq!(vp.scale_factor(), 1.0);
105    }
106
107    #[test]
108    fn construction_and_accessors() {
109        let vp = Viewport::new(1920, 1080, 2.0);
110        assert_eq!(vp.width(), 1920);
111        assert_eq!(vp.height(), 1080);
112        assert_eq!(vp.scale_factor(), 2.0);
113    }
114
115    #[test]
116    fn logical_dimensions() {
117        let vp = Viewport::new(3840, 2160, 2.0);
118        assert_eq!(vp.logical_width(), 1920.0);
119        assert_eq!(vp.logical_height(), 1080.0);
120    }
121
122    #[test]
123    fn logical_dimensions_at_1x() {
124        let vp = Viewport::new(1920, 1080, 1.0);
125        assert_eq!(vp.logical_width(), 1920.0);
126        assert_eq!(vp.logical_height(), 1080.0);
127    }
128
129    #[test]
130    fn resize() {
131        let mut vp = Viewport::new(800, 600, 1.0);
132        vp.resize(1920, 1080);
133        assert_eq!(vp.width(), 1920);
134        assert_eq!(vp.height(), 1080);
135    }
136
137    #[test]
138    fn set_scale_factor() {
139        let mut vp = Viewport::new(1920, 1080, 1.0);
140        vp.set_scale_factor(1.5);
141        assert_eq!(vp.scale_factor(), 1.5);
142        assert!((vp.logical_width() - 1280.0).abs() < 0.01);
143    }
144}