Skip to main content

beyonder_gpu/
viewport.rs

1//! Viewport and scroll management for the block stream.
2
3/// Tracks the visible portion of the block stream.
4#[derive(Debug, Clone)]
5pub struct Viewport {
6    pub width: f32,
7    pub height: f32,
8    pub scroll_offset: f32,
9    pub total_content_height: f32,
10    /// Physical-pixel offset from the top of the window where the block stream begins.
11    /// Lets a tab strip (or other chrome) render above the blocks without overlap.
12    pub top_offset: f32,
13    /// True when the user is at (or near) the bottom and auto-scroll should track new content.
14    /// Set to false when the user explicitly scrolls up; restored when they return to the bottom.
15    pub pinned_to_bottom: bool,
16}
17
18impl Viewport {
19    pub fn new(width: f32, height: f32) -> Self {
20        Self {
21            width,
22            height,
23            scroll_offset: 0.0,
24            total_content_height: 0.0,
25            top_offset: 0.0,
26            pinned_to_bottom: true,
27        }
28    }
29
30    /// Scroll by delta pixels (positive = down).
31    pub fn scroll(&mut self, delta: f32) {
32        let max_scroll = (self.total_content_height - self.height).max(0.0);
33        self.scroll_offset = (self.scroll_offset + delta).clamp(0.0, max_scroll);
34        self.pinned_to_bottom = (max_scroll - self.scroll_offset) < 1.0;
35    }
36
37    /// Snap to the top.
38    pub fn scroll_to_top(&mut self) {
39        self.scroll_offset = 0.0;
40        self.pinned_to_bottom = false;
41    }
42
43    /// Snap to the bottom (show latest content).
44    pub fn scroll_to_bottom(&mut self) {
45        let max_scroll = (self.total_content_height - self.height).max(0.0);
46        self.scroll_offset = max_scroll;
47        self.pinned_to_bottom = true;
48    }
49
50    /// Scroll so a content-space y position is visible, aligned near the top.
51    pub fn scroll_to(&mut self, y: f32) {
52        let max_scroll = (self.total_content_height - self.height).max(0.0);
53        self.scroll_offset = y.clamp(0.0, max_scroll);
54        self.pinned_to_bottom = (max_scroll - self.scroll_offset) < 1.0;
55    }
56
57    pub fn resize(&mut self, width: f32, height: f32) {
58        self.width = width;
59        self.height = height;
60    }
61
62    /// Is a y-coordinate (in content space) visible?
63    pub fn is_visible(&self, y: f32, h: f32) -> bool {
64        let top = self.scroll_offset;
65        let bottom = self.scroll_offset + self.height;
66        y + h > top && y < bottom
67    }
68
69    /// Convert content-space y to screen-space y.
70    pub fn content_to_screen_y(&self, content_y: f32) -> f32 {
71        content_y - self.scroll_offset + self.top_offset
72    }
73}