1use crate::{
9 App, Bounds, Corners, Div, Element, ElementId, GlobalElementId, GpuRenderer, HitboxBehavior,
10 HitboxFilter, ImportedTextureHandle, InteractiveElement, Interactivity, IntoElement, LayoutId,
11 PaintSurface, ParentElement, Pixels, Point, StyleRefinement, Styled, SurfaceSource, Window,
12 div,
13};
14use std::rc::Rc;
15
16pub fn gpu_canvas(content: impl 'static + FnOnce(&mut GpuCanvasContext)) -> GpuCanvas {
25 GpuCanvas {
26 div: div(),
27 content: Some(Box::new(content)),
28 covers: None,
29 }
30}
31
32pub struct GpuCanvasContext<'a, 'b> {
38 window: &'a mut Window<'b>,
39 cx: &'a mut App,
40 bounds: Bounds<Pixels>,
41}
42
43impl GpuCanvasContext<'_, '_> {
44 pub fn paint_surface(&mut self, source: impl Into<SurfaceSource>) {
47 self.window.core.invalidator.debug_assert_paint();
48
49 let bounds = self.window.snap_bounds(self.bounds);
50 let content_mask = self.window.snapped_content_mask();
51 self.window
52 .frame_state
53 .next_frame
54 .scene
55 .insert_primitive(PaintSurface {
56 order: 0,
57 bounds,
58 content_mask,
59 source: source.into(),
60 });
61 }
62
63 pub fn paint_texture(
65 &mut self,
66 handle: ImportedTextureHandle,
67 corner_radii: Corners<Pixels>,
68 opacity: f32,
69 flip_v: bool,
70 ) {
71 self.window
72 .paint_imported_texture(handle, self.bounds, corner_radii, opacity, flip_v);
73 }
74
75 pub fn bounds(&self) -> Bounds<Pixels> {
77 self.bounds
78 }
79
80 pub fn device<R: GpuRenderer>(&mut self) -> R::Device {
88 self.try_device::<R>().unwrap_or_else(|| {
89 panic!(
90 "the window's renderer is not a {}: use try_device to handle this",
91 std::any::type_name::<R>(),
92 )
93 })
94 }
95
96 pub fn try_device<R: GpuRenderer>(&mut self) -> Option<R::Device> {
103 let mut lent = None;
104 self.window.core.platform_window.with_renderer(&mut |renderer| {
105 lent = renderer.as_renderer::<R>().and_then(GpuRenderer::device);
106 });
107 lent
108 }
109
110 pub fn cx(&mut self) -> &mut App {
112 self.cx
113 }
114}
115
116impl<'a, 'b> GpuCanvasContext<'a, 'b> {
117 pub(crate) fn new(
118 window: &'a mut Window<'b>,
119 cx: &'a mut App,
120 bounds: Bounds<Pixels>,
121 ) -> Self {
122 Self {
123 window,
124 cx,
125 bounds,
126 }
127 }
128}
129
130pub struct GpuCanvas {
136 div: Div,
137 content: Option<Box<dyn FnOnce(&mut GpuCanvasContext)>>,
138 covers: Option<Rc<dyn Fn(Point<f32>) -> bool>>,
141}
142
143impl GpuCanvas {
144 pub fn occlude_where(mut self, covers: impl Fn(Point<f32>) -> bool + 'static) -> Self {
159 self.covers = Some(Rc::new(covers));
160 self
161 }
162}
163
164impl Styled for GpuCanvas {
165 fn style(&mut self) -> &mut StyleRefinement {
166 self.div.style()
167 }
168}
169
170impl InteractiveElement for GpuCanvas {
171 fn interactivity(&mut self) -> &mut Interactivity {
172 self.div.interactivity()
173 }
174}
175
176impl ParentElement for GpuCanvas {
177 fn extend(&mut self, elements: impl IntoIterator<Item = crate::AnyElement>) {
178 self.div.extend(elements)
179 }
180}
181
182impl IntoElement for GpuCanvas {
183 type Element = Self;
184
185 fn into_element(self) -> Self::Element {
186 self
187 }
188}
189
190impl Element for GpuCanvas {
191 type RequestLayoutState = <Div as Element>::RequestLayoutState;
192 type PrepaintState = <Div as Element>::PrepaintState;
193
194 fn id(&self) -> Option<ElementId> {
195 <Div as Element>::id(&self.div)
196 }
197
198 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
199 self.div.source_location()
200 }
201
202 fn a11y_role(&self) -> Option<accesskit::Role> {
203 self.div.a11y_role()
204 }
205
206 fn write_a11y_info(&self, node: &mut accesskit::Node) {
207 self.div.write_a11y_info(node);
208 }
209
210 fn a11y_synthetic_children(
211 &mut self,
212 prepaint: &mut Self::PrepaintState,
213 builder: &mut crate::A11ySubtreeBuilder,
214 ) {
215 self.div.a11y_synthetic_children(prepaint, builder);
216 }
217
218 fn request_layout(
219 &mut self,
220 id: Option<&GlobalElementId>,
221 window: &mut Window,
222 cx: &mut App,
223 ) -> (LayoutId, Self::RequestLayoutState) {
224 self.div.request_layout(id, window, cx)
225 }
226
227 fn prepaint(
228 &mut self,
229 id: Option<&GlobalElementId>,
230 bounds: Bounds<Pixels>,
231 request_layout: &mut Self::RequestLayoutState,
232 window: &mut Window,
233 cx: &mut App,
234 ) -> Self::PrepaintState {
235 if let Some(covers) = self.covers.clone() {
240 let (origin, size) = (bounds.origin, bounds.size);
241 let (width, height) = (size.width.as_f32(), size.height.as_f32());
242 let interactivity = self.div.interactivity();
243 interactivity.hitbox_behavior = HitboxBehavior::BlockMouse;
244 interactivity.hitbox_filter = Some(HitboxFilter::new(move |position| {
245 if width <= 0.0 || height <= 0.0 {
246 return false;
247 }
248 let uv = Point {
249 x: (position.x - origin.x).as_f32() / width,
250 y: (position.y - origin.y).as_f32() / height,
251 };
252 covers(uv)
253 }));
254 }
255
256 self.div.prepaint(id, bounds, request_layout, window, cx)
257 }
258
259 fn paint(
260 &mut self,
261 id: Option<&GlobalElementId>,
262 bounds: Bounds<Pixels>,
263 request_layout: &mut Self::RequestLayoutState,
264 prepaint: &mut Self::PrepaintState,
265 window: &mut Window,
266 cx: &mut App,
267 ) {
268 self.div
271 .paint(id, bounds, request_layout, prepaint, window, cx);
272
273 if let Some(content) = self.content.take() {
274 content(&mut GpuCanvasContext::new(window, cx, bounds));
275 }
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282 use crate::{
283 AnyWindowHandle, AppContext as _, Context, InputEvent, Modifiers, MouseButton,
284 MouseDownEvent, MouseUpEvent, Render, StatefulInteractiveElement, TestAppContext, point,
285 px, size,
286 };
287 use std::{cell::Cell, rc::Rc};
288
289 struct OccludeWhereTestView {
290 clicks: Rc<Cell<usize>>,
291 painted: Rc<Cell<Option<Bounds<Pixels>>>>,
292 }
293
294 impl Render for OccludeWhereTestView {
295 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
296 let clicks = self.clicks.clone();
297 let painted = self.painted.clone();
298 div()
299 .relative()
300 .size_full()
301 .child(
303 div()
304 .id("behind")
305 .absolute()
306 .size_full()
307 .on_click(move |_, _, _| clicks.set(clicks.get() + 1)),
308 )
309 .child(
311 gpu_canvas(move |gpu| painted.set(Some(gpu.bounds())))
312 .absolute()
313 .size_full()
314 .occlude_where(|uv| uv.x < 0.5),
315 )
316 }
317 }
318
319 #[gpui::test]
320 fn occlude_where_claims_the_pointer_only_over_content(cx: &mut TestAppContext) {
321 let clicks = Rc::new(Cell::new(0));
322 let painted = Rc::new(Cell::new(None));
323 let window = cx.open_window(size(px(200.), px(200.)), {
324 let clicks = clicks.clone();
325 let painted = painted.clone();
326 move |_, _| OccludeWhereTestView { clicks, painted }
327 });
328 let window = AnyWindowHandle::from(window);
329
330 let draw = |cx: &mut TestAppContext| {
331 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
332 .unwrap();
333 };
334 let click = |cx: &mut TestAppContext, position| {
335 cx.update_window(window, |_, window, cx| {
336 window.dispatch_event(
337 MouseDownEvent {
338 position,
339 modifiers: Modifiers::default(),
340 button: MouseButton::Left,
341 click_count: 1,
342 first_mouse: false,
343 }
344 .to_platform_input(),
345 cx,
346 );
347 window.dispatch_event(
348 MouseUpEvent {
349 position,
350 modifiers: Modifiers::default(),
351 button: MouseButton::Left,
352 click_count: 1,
353 }
354 .to_platform_input(),
355 cx,
356 );
357 })
358 .unwrap();
359 };
360
361 draw(cx);
365 let painted = painted.get().expect("the canvas painted");
366 let over_content = point(
367 painted.origin.x + px(painted.size.width.as_f32() * 0.25),
368 painted.origin.y + px(painted.size.height.as_f32() * 0.5),
369 );
370 let over_nothing = point(
371 painted.origin.x + px(painted.size.width.as_f32() * 0.75),
372 painted.origin.y + px(painted.size.height.as_f32() * 0.5),
373 );
374
375 click(cx, over_content);
379 assert_eq!(
380 clicks.get(),
381 0,
382 "a click over the canvas's content must not reach the control behind it"
383 );
384
385 click(cx, over_nothing);
386 assert_eq!(
387 clicks.get(),
388 1,
389 "a click over the empty half must pass through to the control behind it"
390 );
391 }
392}