metal_view.rs

  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// ```