Skip to main content

gpui/elements/
surface.rs

1#[cfg(target_os = "windows")]
2use crate::WindowsScreenCaptureFrame;
3use crate::{
4    App, Bounds, DevicePixels, Element, ElementId, GlobalElementId, InspectorElementId,
5    IntoElement, LayoutId, ObjectFit, Pixels, Size, Style, StyleRefinement, Styled, Window,
6};
7#[cfg(any(target_os = "macos", target_os = "ios"))]
8use objc2_core_foundation::CFRetained;
9#[cfg(any(target_os = "macos", target_os = "ios"))]
10use objc2_core_video::{CVPixelBuffer, CVPixelBufferGetHeight, CVPixelBufferGetWidth};
11use refineable::Refineable;
12
13/// The frame that a [`Surface`] shows.
14///
15/// Each variant exists only on the platforms that can show it. On macOS, pass a `CVPixelBuffer`.
16/// On Windows, pass a Windows Graphics Capture frame. On Linux and FreeBSD, or with the
17/// `custom-gpu` feature, pass a GPU texture.
18#[derive(Clone)]
19pub enum SurfaceSource {
20    /// A macOS image buffer from CoreVideo.
21    #[cfg(any(target_os = "macos", target_os = "ios"))]
22    Surface(CFRetained<CVPixelBuffer>),
23    /// A GPU texture, with its size in device pixels.
24    #[cfg(any(
25        target_os = "linux",
26        target_os = "freebsd",
27        all(target_os = "macos", feature = "custom-gpu"),
28        all(target_family = "wasm", feature = "custom-gpu")
29    ))]
30    Texture {
31        /// The GPU texture, type-erased (expected to be `Arc<wgpu::Texture>`)
32        #[cfg(not(target_family = "wasm"))]
33        texture: std::sync::Arc<dyn std::any::Any + Send + Sync>,
34        /// The GPU texture, type-erased (expected to be `Arc<wgpu::Texture>`).
35        ///
36        /// WGPU handles are intentionally thread-local in browser builds.
37        #[cfg(target_family = "wasm")]
38        texture: std::sync::Arc<dyn std::any::Any>,
39        /// Dimensions of the texture in device pixels
40        size: Size<DevicePixels>,
41    },
42    /// A native Windows Graphics Capture texture.
43    #[cfg(target_os = "windows")]
44    WindowsCapture(WindowsScreenCaptureFrame),
45    /// A placeholder for platforms that cannot import native surfaces.
46    #[doc(hidden)]
47    Unsupported(Size<DevicePixels>),
48}
49
50impl std::fmt::Debug for SurfaceSource {
51    fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match *self {
53            #[cfg(any(target_os = "macos", target_os = "ios"))]
54            SurfaceSource::Surface(ref buf) => _f.debug_tuple("Surface").field(buf).finish(),
55            #[cfg(any(
56                target_os = "linux",
57                target_os = "freebsd",
58                all(target_os = "macos", feature = "custom-gpu"),
59                all(target_family = "wasm", feature = "custom-gpu")
60            ))]
61            SurfaceSource::Texture { size, .. } => _f
62                .debug_struct("Texture")
63                .field("size", &size)
64                .finish_non_exhaustive(),
65            #[cfg(target_os = "windows")]
66            SurfaceSource::WindowsCapture(ref frame) => frame.fmt(_f),
67            SurfaceSource::Unsupported(size) => _f.debug_tuple("Unsupported").field(&size).finish(),
68        }
69    }
70}
71
72impl SurfaceSource {
73    fn size(&self) -> Size<DevicePixels> {
74        match self {
75            #[cfg(any(target_os = "macos", target_os = "ios"))]
76            SurfaceSource::Surface(buffer) => crate::size(
77                CVPixelBufferGetWidth(buffer).into(),
78                CVPixelBufferGetHeight(buffer).into(),
79            ),
80            #[cfg(any(
81                target_os = "linux",
82                target_os = "freebsd",
83                all(target_os = "macos", feature = "custom-gpu"),
84                all(target_family = "wasm", feature = "custom-gpu")
85            ))]
86            SurfaceSource::Texture { size, .. } => *size,
87            #[cfg(target_os = "windows")]
88            SurfaceSource::WindowsCapture(frame) => frame.size(),
89            SurfaceSource::Unsupported(size) => *size,
90        }
91    }
92}
93
94#[cfg(any(target_os = "macos", target_os = "ios"))]
95impl From<CFRetained<CVPixelBuffer>> for SurfaceSource {
96    fn from(value: CFRetained<CVPixelBuffer>) -> Self {
97        SurfaceSource::Surface(value)
98    }
99}
100
101#[cfg(target_os = "windows")]
102impl From<WindowsScreenCaptureFrame> for SurfaceSource {
103    fn from(value: WindowsScreenCaptureFrame) -> Self {
104        SurfaceSource::WindowsCapture(value)
105    }
106}
107
108#[cfg(all(target_os = "windows", feature = "screen-capture"))]
109impl From<crate::ScreenCaptureFrame> for SurfaceSource {
110    fn from(value: crate::ScreenCaptureFrame) -> Self {
111        SurfaceSource::WindowsCapture(value.0)
112    }
113}
114
115/// An element that shows a platform video or capture frame, such as a camera feed.
116///
117/// Create one with [`surface`]. The frame is scaled into the element's bounds with [`ObjectFit`],
118/// and the element's opacity applies to it. Corner radii are not applied yet.
119pub struct Surface {
120    source: SurfaceSource,
121    object_fit: ObjectFit,
122    style: StyleRefinement,
123}
124
125/// Creates a [`Surface`] that shows `source`.
126///
127/// The frame is scaled with [`ObjectFit::Contain`] unless you change it with [`Surface::object_fit`].
128/// Size the element with [`Styled`] methods, such as `.size_full()`, because the frame doesn't
129/// give it a size.
130pub fn surface(source: impl Into<SurfaceSource>) -> Surface {
131    Surface {
132        source: source.into(),
133        object_fit: ObjectFit::Contain,
134        style: Default::default(),
135    }
136}
137
138impl Surface {
139    /// Sets how the frame is scaled to the element's bounds.
140    pub fn object_fit(mut self, object_fit: ObjectFit) -> Self {
141        self.object_fit = object_fit;
142        self
143    }
144}
145
146impl Element for Surface {
147    type RequestLayoutState = ();
148    type PrepaintState = ();
149
150    fn id(&self) -> Option<ElementId> {
151        None
152    }
153
154    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
155        None
156    }
157
158    fn request_layout(
159        &mut self,
160        _global_id: Option<&GlobalElementId>,
161        _inspector_id: Option<&InspectorElementId>,
162        window: &mut Window,
163        cx: &mut App,
164    ) -> (LayoutId, Self::RequestLayoutState) {
165        let mut style = Style::default();
166        style.refine(&self.style);
167        let layout_id = window.request_layout(style, [], cx);
168        (layout_id, ())
169    }
170
171    fn prepaint(
172        &mut self,
173        _global_id: Option<&GlobalElementId>,
174        _inspector_id: Option<&InspectorElementId>,
175        _bounds: Bounds<Pixels>,
176        _request_layout: &mut Self::RequestLayoutState,
177        _window: &mut Window,
178        _cx: &mut App,
179    ) -> Self::PrepaintState {
180    }
181
182    fn paint(
183        &mut self,
184        _global_id: Option<&GlobalElementId>,
185        _inspector_id: Option<&InspectorElementId>,
186        #[cfg_attr(
187            not(any(target_os = "macos", target_os = "ios")),
188            allow(unused_variables)
189        )]
190        _bounds: Bounds<Pixels>,
191        _: &mut Self::RequestLayoutState,
192        _: &mut Self::PrepaintState,
193        _window: &mut Window,
194        _: &mut App,
195    ) {
196        let new_bounds = self.object_fit.get_bounds(_bounds, self.source.size());
197        // TODO: Add support for corner_radii.
198        let mut style = Style::default();
199        style.refine(&self.style);
200        _window.with_element_opacity(style.opacity, |window| {
201            window.paint_surface(new_bounds, self.source.clone());
202        });
203    }
204}
205
206impl IntoElement for Surface {
207    type Element = Self;
208
209    fn into_element(self) -> Self::Element {
210        self
211    }
212}
213
214impl Styled for Surface {
215    fn style(&mut self) -> &mut StyleRefinement {
216        &mut self.style
217    }
218}