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}