Skip to main content

gpui_kit/media/
mod.rs

1//! Sound, moving pictures, and geometry — none of it decoded here.
2//!
3//! The three surfaces in this module are the ones a general-purpose desktop
4//! application needs and cannot assemble out of a button and a slider: a
5//! player for audio, a player for video, and a viewer for a 3D model. They
6//! keep the posture the rest of the library keeps, which for media means
7//! something specific.
8//!
9//! **There is no player in this crate, and there is none in GPUI at the
10//! pinned revision.** GPUI draws images and, on macOS, composites a
11//! `CVPixelBuffer` through its surface element; it has no decoder, no audio
12//! device, and no frame pump. So [`AudioPlayer`] and [`VideoPlayer`] are
13//! written against [`MediaTransport`], the seam an operating-system backend
14//! lands behind, and a surface with no transport says exactly that instead of
15//! drawing a transport bar that would move if anything were playing.
16//!
17//! **A progress bar is never drawn from a guess.** Position, duration, and
18//! buffered spans are the transport's facts. A transport that reports no
19//! duration gets a position and no fraction, and a surface with no transport
20//! gets no track at all.
21//!
22//! **A fixture says so.** [`FixtureTransport`] decodes nothing and advances no
23//! clock. Every surface publishes and draws [`MediaOrigin`], so a scene, a
24//! test, and a screenshot all distinguish a fixture from a player.
25//!
26//! **A model is read inside a fence.** [`ModelViewer`] takes a glTF 2.0
27//! document through the bounded reader in [`gltf`], which accepts a stated
28//! subset, refuses anything outside it, and refuses anything past
29//! [`ModelBounds`] before allocating for it. The refusal is the contract; the
30//! shading is deliberately minimal.
31
32pub mod audio_player;
33pub mod gltf;
34pub mod model_viewer;
35pub mod transport;
36pub mod video_player;
37
38pub use audio_player::AudioPlayer;
39pub use gltf::{ModelBounds, ModelDefect, ModelError, ModelLimit, ModelMesh, ModelScene};
40pub use model_viewer::{ModelShading, ModelState, ModelViewer, ModelViewerEvent};
41pub use transport::{
42    FixtureTransport, MediaAvailability, MediaCommand, MediaEvent, MediaOrigin, MediaOutcome,
43    MediaSnapshot, MediaTransport,
44};
45pub use video_player::VideoPlayer;
46
47use gpui::{AnyElement, Hsla, IntoElement, ParentElement, SharedString, Styled, div, px};
48use gpui_kit_theme::{Space, Theme, TypeScale};
49
50use crate::foundation::{StyledExt, text};
51
52/// Where a notice sits on the surface it covers.
53#[derive(Clone, Copy, PartialEq, Eq)]
54pub(crate) enum NoticePlace {
55    /// The middle of an otherwise empty surface.
56    Middle,
57    /// A band along the foot, for a surface with a still behind it that the
58    /// reader is meant to keep seeing.
59    Foot,
60}
61
62/// What stands where the media would be when there is none.
63///
64/// A title and the backend's own sentence, never an empty rectangle: a reader
65/// shown a blank frame cannot tell a refusal from silence.
66fn notice(theme: &Theme, tint: Hsla, title: SharedString, detail: SharedString) -> AnyElement {
67    notice_at(theme, tint, title, detail, NoticePlace::Middle)
68}
69
70/// The same sentence, placed against whatever is already on the surface.
71fn notice_at(
72    theme: &Theme,
73    tint: Hsla,
74    title: SharedString,
75    detail: SharedString,
76    place: NoticePlace,
77) -> AnyElement {
78    let base = match place {
79        NoticePlace::Middle => div().absolute().inset_0().justify_center(),
80        // A still is worth keeping visible, so the sentence takes a band at
81        // the foot on a scrim of its own rather than covering the picture.
82        NoticePlace::Foot => div()
83            .absolute()
84            .bottom_0()
85            .left_0()
86            .right_0()
87            .bg(theme.colors.canvas.opacity(0.88)),
88    };
89    base.column()
90        .items_center()
91        .gap_token(theme, Space::Xs)
92        .p_token(theme, Space::Lg)
93        .text_align(gpui::TextAlign::Center)
94        .child(text(theme, TypeScale::Subtitle, title))
95        .child(
96            text(theme, TypeScale::Body, detail)
97                .max_w(px(360.0))
98                .text_color(tint),
99        )
100        .into_any_element()
101}