use gpui::{
AbsoluteLength, AnyElement, App, Bounds, Corners, Element, GlobalElementId, InspectorElementId,
IntoElement, LayoutId, Pixels, Styled, Window, fill, px,
};
use theme::{SurfaceSpec, SurfaceStyle, Theme};
pub fn of(corner_radius: f32, style: SurfaceStyle, child: impl IntoElement) -> Surface {
Surface {
corners: uniform(corner_radius),
glass: Look::Shipped(style),
tint: None,
child: child.into_any_element(),
}
}
pub fn popover(corner_radius: f32, child: impl IntoElement) -> Surface {
Surface {
corners: uniform(corner_radius),
glass: Look::Popover,
tint: None,
child: child.into_any_element(),
}
}
fn uniform(radius: f32) -> Corners<AbsoluteLength> {
let radius = AbsoluteLength::from(px(radius));
Corners {
top_left: radius,
top_right: radius,
bottom_right: radius,
bottom_left: radius,
}
}
#[derive(Clone, Copy)]
enum Look {
Popover,
Shipped(SurfaceStyle),
Tuned(Tokens),
}
impl Look {
fn tokens(self, theme: &Theme) -> Tokens {
match self {
Look::Popover => Tokens::of(theme, theme.popover_surface),
Look::Shipped(style) => Tokens::of(theme, style),
Look::Tuned(tokens) => tokens,
}
}
}
#[derive(Clone, Copy)]
struct Tokens {
spec: SurfaceSpec,
magnify: f32,
dispersion: f32,
}
impl Tokens {
fn of(theme: &Theme, style: SurfaceStyle) -> Self {
Self {
spec: style.spec(theme),
magnify: theme.glass_magnify,
dispersion: theme.glass_dispersion,
}
}
}
pub trait Surfaced: Styled + IntoElement + Sized {
fn surface(mut self, theme: &Theme, style: SurfaceStyle) -> Surface {
let corners = corners_of(&mut self);
self.style().background = None;
Surface {
corners,
glass: Look::Tuned(Tokens::of(theme, style)),
tint: None,
child: self.into_any_element(),
}
}
}
fn corners_of(styled: &mut impl Styled) -> Corners<AbsoluteLength> {
let square = AbsoluteLength::from(px(0.0));
let radii = &styled.style().corner_radii;
Corners {
top_left: radii.top_left.unwrap_or(square),
top_right: radii.top_right.unwrap_or(square),
bottom_right: radii.bottom_right.unwrap_or(square),
bottom_left: radii.bottom_left.unwrap_or(square),
}
}
impl<E: Styled + IntoElement> Surfaced for E {}
pub struct Surface {
tint: Option<gpui::Hsla>,
corners: Corners<AbsoluteLength>,
glass: Look,
child: AnyElement,
}
const LENSED: bool = cfg!(any(target_os = "macos", target_family = "wasm"));
pub fn lensed(theme: &Theme) -> bool {
LENSED && theme.glass
}
impl Surface {
pub fn tint(mut self, color: gpui::Hsla) -> Self {
self.tint = Some(color);
self
}
fn corners(&self, bounds: Bounds<Pixels>, rem: Pixels) -> Corners<Pixels> {
Corners {
top_left: self.corners.top_left.to_pixels(rem),
top_right: self.corners.top_right.to_pixels(rem),
bottom_right: self.corners.bottom_right.to_pixels(rem),
bottom_left: self.corners.bottom_left.to_pixels(rem),
}
.clamp_radii_for_quad_size(bounds.size)
}
}
impl Element for Surface {
type RequestLayoutState = ();
type PrepaintState = ();
fn id(&self) -> Option<gpui::ElementId> {
None
}
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, ()) {
(self.child.request_layout(window, cx), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) {
self.child.prepaint(window, cx);
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
let theme = Theme::of(cx);
let glass = self.glass.tokens(theme);
let corners = self.corners(bounds, window.rem_size());
if !lensed(theme) {
let tint = self.tint.unwrap_or(glass.spec.tint);
window.paint_quad(fill(bounds, tint).corner_radii(corners));
}
if theme.glass {
let extent = f32::from(bounds.size.width.min(bounds.size.height));
let effect = gpui::GlassEffect {
blur_radius: px(glass.spec.blur),
lens: px(glass.spec.rim.min(extent / 2.0)),
reach: px(glass.spec.reach.min(extent / 2.0)),
gain: glass.spec.gain,
saturation: glass.spec.saturation,
magnify: glass.magnify,
dispersion: glass.dispersion,
tint: self.tint.unwrap_or(glass.spec.tint),
edge: glass.spec.edge,
edge_width: px(glass.spec.edge_width),
edge_aa: px(glass.spec.edge_aa),
};
window.paint_layer(bounds, |window| {
window.paint_backdrop_blur(bounds, corners, effect);
if glass.spec.shadow {
window.paint_drop_shadows_outside(bounds, corners, &theme::surface_shadows());
}
self.child.paint(window, cx);
});
} else {
self.child.paint(window, cx);
}
}
}
impl IntoElement for Surface {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
pub fn layered(child: impl IntoElement) -> Layered {
Layered {
child: child.into_any_element(),
}
}
pub struct Layered {
child: AnyElement,
}
impl Element for Layered {
type RequestLayoutState = ();
type PrepaintState = ();
fn id(&self) -> Option<gpui::ElementId> {
None
}
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, ()) {
(self.child.request_layout(window, cx), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) {
self.child.prepaint(window, cx);
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
window.paint_layer(bounds, |window| self.child.paint(window, cx));
}
}
impl IntoElement for Layered {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}