Skip to main content

ui/
tiled_image.rs

1use gpui::{
2    AnyElement, App, Bounds, ContentMask, Corners, Element, ElementId, GlobalElementId,
3    ImageSource, ImgResourceLoader, InspectorElementId, IntoElement, LayoutId, Pixels, Resource,
4    Size, Style, Window, img, point, prelude::*, px, relative,
5};
6
7pub struct TiledImage {
8    source: Resource,
9    scale: f32,
10}
11
12impl TiledImage {
13    pub fn new(source: impl Into<Resource>) -> Self {
14        Self {
15            source: source.into(),
16            scale: 1.,
17        }
18    }
19
20    pub fn scale(mut self, scale: f32) -> Self {
21        self.scale = scale;
22        self
23    }
24}
25
26impl IntoElement for TiledImage {
27    type Element = Self;
28    fn into_element(self) -> Self {
29        self
30    }
31}
32
33impl Element for TiledImage {
34    type RequestLayoutState = (AnyElement, LayoutId);
35    type PrepaintState = Size<Pixels>;
36
37    fn id(&self) -> Option<ElementId> {
38        None
39    }
40    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
41        None
42    }
43
44    fn request_layout(
45        &mut self,
46        _: Option<&GlobalElementId>,
47        _: Option<&InspectorElementId>,
48        window: &mut Window,
49        cx: &mut App,
50    ) -> (LayoutId, Self::RequestLayoutState) {
51        let mut intrinsic = img(ImageSource::Resource(self.source.clone()))
52            .absolute()
53            .flex_none()
54            .into_any_element();
55        let measured = intrinsic.request_layout(window, cx);
56        let mut style = Style::default();
57        style.size.width = relative(1.).into();
58        style.size.height = relative(1.).into();
59        (
60            window.request_layout(style, [measured], cx),
61            (intrinsic, measured),
62        )
63    }
64
65    fn prepaint(
66        &mut self,
67        _: Option<&GlobalElementId>,
68        _: Option<&InspectorElementId>,
69        _: Bounds<Pixels>,
70        state: &mut Self::RequestLayoutState,
71        window: &mut Window,
72        _: &mut App,
73    ) -> Self::PrepaintState {
74        window
75            .layout_bounds(state.1)
76            .size
77            .map(|length| length * self.scale)
78    }
79
80    fn paint(
81        &mut self,
82        _: Option<&GlobalElementId>,
83        _: Option<&InspectorElementId>,
84        bounds: Bounds<Pixels>,
85        _: &mut Self::RequestLayoutState,
86        tile: &mut Self::PrepaintState,
87        window: &mut Window,
88        cx: &mut App,
89    ) {
90        let (width, height) = (f32::from(tile.width), f32::from(tile.height));
91        if !width.is_finite() || !height.is_finite() || width <= 0. || height <= 0. {
92            return;
93        }
94        let visible = bounds.intersect(&window.content_mask().bounds);
95        if visible.size.width <= px(0.) || visible.size.height <= px(0.) {
96            return;
97        }
98        let Some(Ok(image)) = window.use_asset::<ImgResourceLoader>(&self.source, cx) else {
99            return;
100        };
101        let first_x = (f32::from(visible.left() - bounds.left()) / width).floor() as i64;
102        let first_y = (f32::from(visible.top() - bounds.top()) / height).floor() as i64;
103        let last_x = (f32::from(visible.right() - bounds.left()) / width).ceil() as i64;
104        let last_y = (f32::from(visible.bottom() - bounds.top()) / height).ceil() as i64;
105        window.with_content_mask(
106            Some(ContentMask {
107                bounds: visible,
108                ..Default::default()
109            }),
110            |window| {
111                for y in first_y..last_y {
112                    for x in first_x..last_x {
113                        let origin =
114                            bounds.origin + point(px(x as f32 * width), px(y as f32 * height));
115                        let bounds = Bounds {
116                            origin,
117                            size: *tile,
118                        };
119                        let _ = window.paint_image(
120                            bounds,
121                            bounds,
122                            Corners::default(),
123                            image.clone(),
124                            0,
125                            false,
126                        );
127                    }
128                }
129            },
130        );
131    }
132}