1use crate::{
2 App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
3 Pixels, Style, StyleRefinement, Styled, Window,
4};
5use refineable::Refineable;
6use std::sync::Arc;
7
8#[cfg(target_os = "macos")]
9use metal::{RenderCommandEncoderRef, TextureRef};
10
11/// A callback for custom Metal rendering.
12///
13/// The callback receives:
14/// - command_encoder: The Metal command encoder to issue draw calls
15/// - target_texture: The texture to render into
16/// - bounds: The bounds of the element in pixels
17/// - scale_factor: The window's scale factor
18#[cfg(target_os = "macos")]
19pub type MetalRenderCallback =
20 Arc<dyn Fn(&RenderCommandEncoderRef, &TextureRef, Bounds<Pixels>, f32) + Send + Sync + 'static>;
21
22/// A view that allows custom Metal rendering.
23pub struct MetalView {
24 #[cfg(target_os = "macos")]
25 render_callback: Option<MetalRenderCallback>,
26 style: StyleRefinement,
27}
28
29/// Create a new Metal view element.
30pub fn metal_view() -> MetalView {
31 MetalView {
32 #[cfg(target_os = "macos")]
33 render_callback: None,
34 style: Default::default(),
35 }
36}
37
38impl MetalView {
39 /// Set the Metal render callback.
40 #[cfg(target_os = "macos")]
41 pub fn render_with<F>(mut self, callback: F) -> Self
42 where
43 F: Fn(&RenderCommandEncoderRef, &TextureRef, Bounds<Pixels>, f32) + Send + Sync + 'static,
44 {
45 self.render_callback = Some(Arc::new(callback));
46 self
47 }
48
49 /// Set the Metal render callback using a shared callback.
50 #[cfg(target_os = "macos")]
51 pub fn render_with_shared(mut self, callback: MetalRenderCallback) -> Self {
52 self.render_callback = Some(callback);
53 self
54 }
55}
56
57impl Element for MetalView {
58 type RequestLayoutState = ();
59 type PrepaintState = ();
60
61 fn id(&self) -> Option<ElementId> {
62 None
63 }
64
65 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
66 None
67 }
68
69 fn request_layout(
70 &mut self,
71 _global_id: Option<&GlobalElementId>,
72 _inspector_id: Option<&InspectorElementId>,
73 window: &mut Window,
74 cx: &mut App,
75 ) -> (LayoutId, Self::RequestLayoutState) {
76 let mut style = Style::default();
77 style.refine(&self.style);
78 let layout_id = window.request_layout(style, [], cx);
79 (layout_id, ())
80 }
81
82 fn prepaint(
83 &mut self,
84 _global_id: Option<&GlobalElementId>,
85 _inspector_id: Option<&InspectorElementId>,
86 _bounds: Bounds<Pixels>,
87 _request_layout: &mut Self::RequestLayoutState,
88 _window: &mut Window,
89 _cx: &mut App,
90 ) -> Self::PrepaintState {
91 }
92
93 fn paint(
94 &mut self,
95 _global_id: Option<&GlobalElementId>,
96 _inspector_id: Option<&InspectorElementId>,
97 bounds: Bounds<Pixels>,
98 _: &mut Self::RequestLayoutState,
99 _: &mut Self::PrepaintState,
100 window: &mut Window,
101 _: &mut App,
102 ) {
103 #[cfg(target_os = "macos")]
104 if let Some(render_callback) = &self.render_callback {
105 // TODO: This is a placeholder. In a real implementation, we would need to:
106 // 1. Register this Metal view with the window's rendering system
107 // 2. Ensure the callback is invoked during the Metal rendering pass
108 // 3. Handle proper clipping and transformation matrices
109 //
110 // For now, we'll store the callback and bounds in the window's custom render queue
111 window.paint_metal_view(bounds, render_callback.clone());
112 }
113 }
114}
115
116impl IntoElement for MetalView {
117 type Element = Self;
118
119 fn into_element(self) -> Self::Element {
120 self
121 }
122}
123
124impl Styled for MetalView {
125 fn style(&mut self) -> &mut StyleRefinement {
126 &mut self.style
127 }
128}
129
130/// Extension trait for MetalView to provide platform-agnostic API
131pub trait MetalViewExt {
132 /// Set a placeholder render function for non-macOS platforms
133 fn render_placeholder<F>(self, callback: F) -> Self
134 where
135 F: Fn(Bounds<Pixels>) + Send + Sync + 'static;
136}
137
138impl MetalViewExt for MetalView {
139 fn render_placeholder<F>(self, _callback: F) -> Self
140 where
141 F: Fn(Bounds<Pixels>) + Send + Sync + 'static,
142 {
143 // On non-macOS platforms, this could render a placeholder
144 // or use a different rendering backend
145 self
146 }
147}
148
149#[cfg(target_os = "macos")]
150/// Helper functions for creating common Metal render callbacks
151pub mod helpers {
152 use super::*;
153 use metal::*;
154
155 /// Helper to create a simple colored rectangle Metal renderer
156 pub fn solid_color_renderer(r: f32, g: f32, b: f32, a: f32) -> MetalRenderCallback {
157 Arc::new(move |encoder, _texture, bounds, _scale_factor| {
158 // This is a simplified example. In practice, you would:
159 // 1. Create or reuse a render pipeline state
160 // 2. Set up vertex data for the bounds
161 // 3. Issue draw calls
162 // 4. Handle proper coordinate transformation
163
164 // For now, this is just a placeholder to show the API design
165 let _ = (encoder, bounds, r, g, b, a);
166 })
167 }
168
169 /// Helper to create a Metal renderer that draws a textured quad
170 pub fn textured_quad_renderer(texture: Texture) -> MetalRenderCallback {
171 Arc::new(move |encoder, _target, bounds, _scale_factor| {
172 // Similar to above, this would set up a textured quad rendering
173 let _ = (encoder, &texture, bounds);
174 })
175 }
176}
177
178// Example usage:
179// ```rust
180// use gpui::elements::{metal_view, MetalViewExt};
181//
182// #[cfg(target_os = "macos")]
183// let view = metal_view()
184// .render_with(|encoder, target, bounds, scale_factor| {
185// // Custom Metal rendering code here
186// // You have full access to Metal command encoder
187// })
188// .size_full();
189//
190// #[cfg(not(target_os = "macos"))]
191// let view = metal_view()
192// .render_placeholder(|bounds| {
193// // Fallback rendering for non-macOS platforms
194// })
195// .size_full();
196// ```