Detailed changes
@@ -52,6 +52,8 @@ fn generate_shader_bindings() -> PathBuf {
"AtlasTile".into(),
"QuadInputIndex".into(),
"Quad".into(),
+ "ShadowInputIndex".into(),
+ "Shadow".into(),
"SpriteInputIndex".into(),
"MonochromeSprite".into(),
"PolychromeSprite".into(),
@@ -469,6 +469,17 @@ impl Edges<AbsoluteLength> {
}
}
+impl Edges<Pixels> {
+ pub fn scale(&self, factor: f32) -> Edges<ScaledPixels> {
+ Edges {
+ top: self.top.scale(factor),
+ right: self.right.scale(factor),
+ bottom: self.bottom.scale(factor),
+ left: self.left.scale(factor),
+ }
+ }
+}
+
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(debug)]
#[repr(C)]
@@ -480,8 +491,8 @@ pub struct Corners<T: Clone + Debug> {
}
impl Corners<AbsoluteLength> {
- pub fn to_pixels(&self, bounds: Bounds<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
- let max = bounds.size.width.max(bounds.size.height) / 2.;
+ pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
+ let max = size.width.max(size.height) / 2.;
Corners {
top_left: self.top_left.to_pixels(rem_size).min(max),
top_right: self.top_right.to_pixels(rem_size).min(max),
@@ -1,6 +1,6 @@
use crate::{
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
- Quad, Scene, Size,
+ Quad, Scene, Shadow, Size,
};
use cocoa::{
base::{NO, YES},
@@ -18,6 +18,7 @@ pub struct MetalRenderer {
layer: metal::MetalLayer,
command_queue: CommandQueue,
quads_pipeline_state: metal::RenderPipelineState,
+ shadows_pipeline_state: metal::RenderPipelineState,
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
@@ -90,6 +91,14 @@ impl MetalRenderer {
"quad_fragment",
PIXEL_FORMAT,
);
+ let shadows_pipeline_state = build_pipeline_state(
+ &device,
+ &library,
+ "shadows",
+ "shadow_vertex",
+ "shadow_fragment",
+ PIXEL_FORMAT,
+ );
let monochrome_sprites_pipeline_state = build_pipeline_state(
&device,
&library,
@@ -114,6 +123,7 @@ impl MetalRenderer {
layer,
command_queue,
quads_pipeline_state,
+ shadows_pipeline_state,
monochrome_sprites_pipeline_state,
polychrome_sprites_pipeline_state,
unit_vertices,
@@ -183,6 +193,14 @@ impl MetalRenderer {
command_encoder,
);
}
+ crate::PrimitiveBatch::Shadows(shadows) => {
+ self.draw_shadows(
+ shadows,
+ &mut instance_offset,
+ viewport_size,
+ command_encoder,
+ );
+ }
crate::PrimitiveBatch::MonochromeSprites {
texture_id,
sprites,
@@ -279,6 +297,66 @@ impl MetalRenderer {
*offset = next_offset;
}
+ fn draw_shadows(
+ &mut self,
+ shadows: &[Shadow],
+ offset: &mut usize,
+ viewport_size: Size<DevicePixels>,
+ command_encoder: &metal::RenderCommandEncoderRef,
+ ) {
+ if shadows.is_empty() {
+ return;
+ }
+ align_offset(offset);
+
+ command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
+ command_encoder.set_vertex_buffer(
+ ShadowInputIndex::Vertices as u64,
+ Some(&self.unit_vertices),
+ 0,
+ );
+ command_encoder.set_vertex_buffer(
+ ShadowInputIndex::Shadows as u64,
+ Some(&self.instances),
+ *offset as u64,
+ );
+ command_encoder.set_fragment_buffer(
+ ShadowInputIndex::Shadows as u64,
+ Some(&self.instances),
+ *offset as u64,
+ );
+
+ command_encoder.set_vertex_bytes(
+ ShadowInputIndex::ViewportSize as u64,
+ mem::size_of_val(&viewport_size) as u64,
+ &viewport_size as *const Size<DevicePixels> as *const _,
+ );
+
+ let shadow_bytes_len = mem::size_of::<Shadow>() * shadows.len();
+ let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+ unsafe {
+ ptr::copy_nonoverlapping(
+ shadows.as_ptr() as *const u8,
+ buffer_contents,
+ shadow_bytes_len,
+ );
+ }
+
+ let next_offset = *offset + shadow_bytes_len;
+ assert!(
+ next_offset <= INSTANCE_BUFFER_SIZE,
+ "instance buffer exhausted"
+ );
+
+ command_encoder.draw_primitives_instanced(
+ metal::MTLPrimitiveType::Triangle,
+ 0,
+ 6,
+ shadows.len() as u64,
+ );
+ *offset = next_offset;
+ }
+
fn draw_monochrome_sprites(
&mut self,
texture_id: AtlasTextureId,
@@ -469,6 +547,13 @@ enum QuadInputIndex {
ViewportSize = 2,
}
+#[repr(C)]
+enum ShadowInputIndex {
+ Vertices = 0,
+ Shadows = 1,
+ ViewportSize = 2,
+}
+
#[repr(C)]
enum SpriteInputIndex {
Vertices = 0,
@@ -11,6 +11,9 @@ float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size);
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
Corners_ScaledPixels corner_radii);
+float gaussian(float x, float sigma);
+float2 erf(float2 x);
+float blur_along_x(float x, float y, float sigma, float corner, float2 half_size);
struct QuadVertexOutput {
float4 position [[position]];
@@ -110,6 +113,91 @@ fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
return color * float4(1., 1., 1., saturate(0.5 - distance));
}
+struct ShadowVertexOutput {
+ float4 position [[position]];
+ float4 color [[flat]];
+ uint shadow_id [[flat]];
+};
+
+vertex ShadowVertexOutput shadow_vertex(
+ uint unit_vertex_id [[vertex_id]],
+ uint shadow_id [[instance_id]],
+ constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]],
+ constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]],
+ constant Size_DevicePixels *viewport_size [[buffer(ShadowInputIndex_ViewportSize)]]
+) {
+ float2 unit_vertex = unit_vertices[unit_vertex_id];
+ Shadow shadow = shadows[shadow_id];
+
+ float margin = (3. * shadow.blur_radius) + shadow.spread_radius;
+ // Set the bounds of the shadow and adjust its size based on the shadow's spread radius
+ // to achieve the spreading effect
+ Bounds_ScaledPixels bounds = shadow.bounds;
+ bounds.origin.x -= margin;
+ bounds.origin.y -= margin;
+ bounds.size.width += 2. * margin;
+ bounds.size.height += 2. * margin;
+
+ float4 device_position = to_device_position(unit_vertex, bounds, bounds, viewport_size);
+ float4 color = hsla_to_rgba(shadow.color);
+
+ return ShadowVertexOutput {
+ device_position,
+ color,
+ shadow_id,
+ };
+}
+
+fragment float4 shadow_fragment(
+ ShadowVertexOutput input [[stage_in]],
+ constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]]
+) {
+ Shadow shadow = shadows[input.shadow_id];
+
+ float2 origin = float2(
+ shadow.bounds.origin.x - shadow.spread_radius,
+ shadow.bounds.origin.y - shadow.spread_radius
+ );
+ float2 size = float2(
+ shadow.bounds.size.width + shadow.spread_radius * 2.,
+ shadow.bounds.size.height + shadow.spread_radius * 2.
+ );
+ float2 half_size = size / 2.;
+ float2 center = origin + half_size;
+ float2 point = input.position.xy - center;
+ float corner_radius;
+ if (point.x < 0.) {
+ if (point.y < 0.) {
+ corner_radius = shadow.corner_radii.top_left;
+ } else {
+ corner_radius = shadow.corner_radii.bottom_left;
+ }
+ } else {
+ if (point.y < 0.) {
+ corner_radius = shadow.corner_radii.top_right;
+ } else {
+ corner_radius = shadow.corner_radii.bottom_right;
+ }
+ }
+
+ // The signal is only non-zero in a limited range, so don't waste samples
+ float low = point.y - half_size.y;
+ float high = point.y + half_size.y;
+ float start = clamp(-3. * shadow.blur_radius, low, high);
+ float end = clamp(3. * shadow.blur_radius, low, high);
+
+ // Accumulate samples (we can get away with surprisingly few samples)
+ float step = (end - start) / 4.;
+ float y = start + step * 0.5;
+ float alpha = 0.;
+ for (int i = 0; i < 4; i++) {
+ alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius, corner_radius, half_size) * gaussian(y, shadow.blur_radius) * step;
+ y += step;
+ }
+
+ return input.color * float4(1., 1., 1., alpha);
+}
+
struct MonochromeSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
@@ -308,3 +396,24 @@ float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
return distance;
}
+
+// A standard gaussian function, used for weighting samples
+float gaussian(float x, float sigma) {
+ return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
+}
+
+// This approximates the error function, needed for the gaussian integral
+float2 erf(float2 x) {
+ float2 s = sign(x);
+ float2 a = abs(x);
+ x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
+ x *= x;
+ return s - s / (x * x);
+}
+
+float blur_along_x(float x, float y, float sigma, float corner, float2 half_size) {
+ float delta = min(half_size.y - corner - abs(y), 0.);
+ float curved = half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
+ float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
+ return integral.y - integral.x;
+}
@@ -38,6 +38,9 @@ impl Scene {
Primitive::Quad(quad) => {
layer.quads.push(quad);
}
+ Primitive::Shadow(shadow) => {
+ layer.shadows.push(shadow);
+ }
Primitive::MonochromeSprite(sprite) => {
layer.monochrome_sprites.push(sprite);
}
@@ -55,6 +58,7 @@ impl Scene {
#[derive(Debug, Default)]
pub(crate) struct SceneLayer {
pub quads: Vec<Quad>,
+ pub shadows: Vec<Shadow>,
pub monochrome_sprites: Vec<MonochromeSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>,
}
@@ -68,6 +72,9 @@ impl SceneLayer {
quads: &self.quads,
quads_start: 0,
quads_iter: self.quads.iter().peekable(),
+ shadows: &self.shadows,
+ shadows_start: 0,
+ shadows_iter: self.shadows.iter().peekable(),
monochrome_sprites: &self.monochrome_sprites,
monochrome_sprites_start: 0,
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
@@ -82,6 +89,9 @@ struct BatchIterator<'a> {
quads: &'a [Quad],
quads_start: usize,
quads_iter: Peekable<slice::Iter<'a, Quad>>,
+ shadows: &'a [Shadow],
+ shadows_start: usize,
+ shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
monochrome_sprites: &'a [MonochromeSprite],
monochrome_sprites_start: usize,
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
@@ -96,6 +106,10 @@ impl<'a> Iterator for BatchIterator<'a> {
fn next(&mut self) -> Option<Self::Item> {
let mut kinds_and_orders = [
(PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
+ (
+ PrimitiveKind::Shadow,
+ self.shadows_iter.peek().map(|s| s.order),
+ ),
(
PrimitiveKind::MonochromeSprite,
self.monochrome_sprites_iter.peek().map(|s| s.order),
@@ -127,6 +141,19 @@ impl<'a> Iterator for BatchIterator<'a> {
self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
}
+ PrimitiveKind::Shadow => {
+ let shadows_start = self.shadows_start;
+ let shadows_end = shadows_start
+ + self
+ .shadows_iter
+ .by_ref()
+ .take_while(|shadow| shadow.order <= max_order)
+ .count();
+ self.shadows_start = shadows_end;
+ Some(PrimitiveBatch::Shadows(
+ &self.shadows[shadows_start..shadows_end],
+ ))
+ }
PrimitiveKind::MonochromeSprite => {
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.monochrome_sprites_start;
@@ -168,6 +195,7 @@ impl<'a> Iterator for BatchIterator<'a> {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PrimitiveKind {
Quad,
+ Shadow,
MonochromeSprite,
PolychromeSprite,
}
@@ -175,12 +203,15 @@ pub enum PrimitiveKind {
#[derive(Clone, Debug)]
pub enum Primitive {
Quad(Quad),
+ Shadow(Shadow),
MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite),
}
+#[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> {
Quads(&'a [Quad]),
+ Shadows(&'a [Shadow]),
MonochromeSprites {
texture_id: AtlasTextureId,
sprites: &'a [MonochromeSprite],
@@ -221,6 +252,36 @@ impl From<Quad> for Primitive {
}
}
+#[derive(Debug, Clone, Eq, PartialEq)]
+#[repr(C)]
+pub struct Shadow {
+ pub order: u32,
+ pub bounds: Bounds<ScaledPixels>,
+ pub corner_radii: Corners<ScaledPixels>,
+ pub content_mask: ScaledContentMask,
+ pub color: Hsla,
+ pub blur_radius: ScaledPixels,
+ pub spread_radius: ScaledPixels,
+}
+
+impl Ord for Shadow {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.order.cmp(&other.order)
+ }
+}
+
+impl PartialOrd for Shadow {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl From<Shadow> for Primitive {
+ fn from(shadow: Shadow) -> Self {
+ Primitive::Shadow(shadow)
+ }
+}
+
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct MonochromeSprite {
@@ -1,7 +1,7 @@
use crate::{
phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle,
- FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle,
+ FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle, Shadow,
SharedString, Size, SizeRefinement, ViewContext, WindowContext,
};
use refineable::Refineable;
@@ -89,10 +89,21 @@ pub struct Style {
#[refineable]
pub corner_radii: Corners<AbsoluteLength>,
+ /// Box Shadow of the element
+ pub box_shadow: Option<BoxShadow>,
+
/// TEXT
pub text: TextStyleRefinement,
}
+#[derive(Clone, Debug)]
+pub struct BoxShadow {
+ pub color: Hsla,
+ pub offset: Point<Pixels>,
+ pub blur_radius: Pixels,
+ pub spread_radius: Pixels,
+}
+
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
pub struct TextStyle {
@@ -233,6 +244,28 @@ impl Style {
let rem_size = cx.rem_size();
let scale = cx.scale_factor();
+ if let Some(shadow) = self.box_shadow.as_ref() {
+ let layer_id = cx.current_layer_id();
+ let content_mask = cx.content_mask();
+ let mut shadow_bounds = bounds;
+ shadow_bounds.origin += shadow.offset;
+ cx.scene().insert(
+ layer_id,
+ Shadow {
+ order,
+ bounds: shadow_bounds.scale(scale),
+ content_mask: content_mask.scale(scale),
+ corner_radii: self
+ .corner_radii
+ .to_pixels(bounds.size, rem_size)
+ .scale(scale),
+ color: shadow.color,
+ blur_radius: shadow.blur_radius.scale(scale),
+ spread_radius: shadow.spread_radius.scale(scale),
+ },
+ );
+ }
+
let background_color = self.fill.as_ref().and_then(Fill::color);
if background_color.is_some() || self.is_border_visible() {
let layer_id = cx.current_layer_id();
@@ -247,10 +280,9 @@ impl Style {
border_color: self.border_color.unwrap_or_default(),
corner_radii: self
.corner_radii
- .map(|length| length.to_pixels(rem_size).scale(scale)),
- border_widths: self
- .border_widths
- .map(|length| length.to_pixels(rem_size).scale(scale)),
+ .to_pixels(bounds.size, rem_size)
+ .scale(scale),
+ border_widths: self.border_widths.to_pixels(rem_size).scale(scale),
},
);
}
@@ -296,6 +328,7 @@ impl Default for Style {
fill: None,
border_color: None,
corner_radii: Corners::default(),
+ box_shadow: None,
text: TextStyleRefinement::default(),
}
}
@@ -1,6 +1,7 @@
use crate::{
- self as gpui3, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla, JustifyContent,
- Length, Position, SharedString, Style, StyleRefinement, Styled, TextStyleRefinement,
+ self as gpui3, hsla, point, px, relative, rems, AlignItems, BoxShadow, Display, Fill,
+ FlexDirection, Hsla, JustifyContent, Length, Position, SharedString, Style, StyleRefinement,
+ Styled, TextStyleRefinement,
};
pub trait StyleHelpers: Styled<Style = Style> {
@@ -213,6 +214,58 @@ pub trait StyleHelpers: Styled<Style = Style> {
self
}
+ fn shadow(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self
+ }
+
+ fn shadow_sm(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().box_shadow = Some(BoxShadow {
+ color: hsla(0., 0., 0., 1.),
+ offset: point(px(0.), px(0.)),
+ blur_radius: px(1.),
+ spread_radius: px(0.),
+ });
+ self
+ }
+
+ fn shadow_md(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ todo!();
+ self
+ }
+
+ fn shadow_lg(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ todo!();
+ self
+ }
+
+ fn shadow_xl(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ todo!();
+ self
+ }
+
+ fn shadow_2xl(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ todo!();
+ self
+ }
+
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
let style: &mut StyleRefinement = self.declared_style();
&mut style.text
@@ -168,7 +168,8 @@ impl CollabPanel {
.uri(avatar_uri)
.size_3p5()
.rounded_full()
- .fill(theme.middle.positive.default.foreground),
+ .fill(theme.middle.positive.default.foreground)
+ .shadow_sm(),
)
.child(label),
)