Skip to main content

ferrum_flow/
viewport.rs

1use gpui::{Bounds, Pixels, Point, Size, Window, px};
2
3use crate::Node;
4
5#[derive(Debug, Clone)]
6pub struct Viewport {
7    pub zoom: f32,
8    pub offset: Point<Pixels>,
9    pub window_bounds: Option<Bounds<Pixels>>,
10}
11
12impl Viewport {
13    pub fn new() -> Self {
14        Self {
15            zoom: 1.0,
16            offset: Point::new(px(0.0), px(0.0)),
17            window_bounds: None,
18        }
19    }
20
21    /// Sets [`Self::window_bounds`] to the window’s drawable area (`Window::viewport_size`),
22    /// origin `(0, 0)`. Skips assignment when width/height are unchanged.
23    ///
24    /// Prefer this over `Window::bounds()` for hit-testing and overlay layout: the latter is in
25    /// global space and can be larger than the content viewport.
26    pub fn sync_drawable_bounds(&mut self, window: &Window) {
27        let vs = window.viewport_size();
28        let unchanged = self.window_bounds.is_some_and(|b| {
29            b.size.width == vs.width && b.size.height == vs.height
30        });
31        if !unchanged {
32            self.window_bounds = Some(Bounds::new(
33                Point::new(px(0.0), px(0.0)),
34                Size::new(vs.width, vs.height),
35            ));
36        }
37    }
38
39    pub fn world_to_screen(&self, p: Point<Pixels>) -> Point<Pixels> {
40        Point::new(
41            p.x * self.zoom + self.offset.x,
42            p.y * self.zoom + self.offset.y,
43        )
44    }
45
46    pub fn screen_to_world(&self, p: Point<Pixels>) -> Point<Pixels> {
47        Point::new(
48            (p.x - self.offset.x) / self.zoom,
49            (p.y - self.offset.y) / self.zoom,
50        )
51    }
52
53    pub fn is_node_visible(&self, node: &Node) -> bool {
54        let Some(window_bounds) = self.window_bounds else {
55            return false;
56        };
57
58        let screen = self.world_to_screen(node.point());
59
60        screen.x + node.size.width * self.zoom > px(0.0)
61            && screen.x < window_bounds.size.width
62            && screen.y + node.size.height * self.zoom > px(0.0)
63            && screen.y < window_bounds.size.height
64    }
65}