diff --git a/Cargo.lock b/Cargo.lock index b5cd1fb9cf..dd5b2165b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,8 +295,7 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "anyrender" version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1755d0ef8e00e87b6b825dfbe5a35d34d3b302e97e80a48e93f9cd361fbada06" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1791068178-box-shadow-api#5863bc787e2101cfac98028d954cbcc92b32c789" dependencies = [ "kurbo", "peniko", @@ -308,8 +307,7 @@ dependencies = [ [[package]] name = "anyrender_serialize" version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c94ffeb5bf25fbd3c1ac50a00669b8432baf380904daeb260c9f4aafe44c6c2" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1791068178-box-shadow-api#5863bc787e2101cfac98028d954cbcc92b32c789" dependencies = [ "anyrender", "image", @@ -327,8 +325,7 @@ dependencies = [ [[package]] name = "anyrender_skia" version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282fdae1228486bcdbc973abf6f45afabcbd1579b63b4c95de9da4a0b893b4b7" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1791068178-box-shadow-api#5863bc787e2101cfac98028d954cbcc92b32c789" dependencies = [ "anyrender", "ash", @@ -355,8 +352,7 @@ dependencies = [ [[package]] name = "anyrender_svg" version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39327debd1bdca190d086c107825a5dc4c665be31e99e5c0854345a050d08cad" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1791068178-box-shadow-api#5863bc787e2101cfac98028d954cbcc92b32c789" dependencies = [ "anyrender", "image", @@ -368,8 +364,7 @@ dependencies = [ [[package]] name = "anyrender_vello" version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d545f71a3f13b1099d5787c44cfeb00322cebbebbf90c4f8cdae78bee423f7" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1791068178-box-shadow-api#5863bc787e2101cfac98028d954cbcc92b32c789" dependencies = [ "anyrender", "debug_timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -387,8 +382,7 @@ dependencies = [ [[package]] name = "anyrender_vello_cpu" version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a09d9356dd2b2f032345711a350c0e2326a854cb5070f899265bc80fb33884b1" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1791068178-box-shadow-api#5863bc787e2101cfac98028d954cbcc92b32c789" dependencies = [ "anyrender", "bytemuck", @@ -406,8 +400,7 @@ dependencies = [ [[package]] name = "anyrender_vello_hybrid" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5139465733e457ebd2dd04efbee5aa6fbc1d8747d0e3fe0f81b8de4f240cb01" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1791068178-box-shadow-api#5863bc787e2101cfac98028d954cbcc92b32c789" dependencies = [ "anyrender", "debug_timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -6232,8 +6225,7 @@ dependencies = [ [[package]] name = "pixels_window_renderer" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c2ba47171f8376a1cd74f8d575f1d729c033fc007f59a9da3688c31187264b" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1791068178-box-shadow-api#5863bc787e2101cfac98028d954cbcc92b32c789" dependencies = [ "anyrender", "debug_timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -7585,8 +7577,7 @@ dependencies = [ [[package]] name = "softbuffer_window_renderer" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809f260e686014e0222158362d571a7813cd836a27b7e51ed7e1dcf39ce08378" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1791068178-box-shadow-api#5863bc787e2101cfac98028d954cbcc92b32c789" dependencies = [ "anyrender", "debug_timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -9683,8 +9674,7 @@ dependencies = [ [[package]] name = "wgpu_context" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5277f9d30e7087f1eeb0034206dce7d3377e76da017fdceb8aa4faebb1df68e2" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1791068178-box-shadow-api#5863bc787e2101cfac98028d954cbcc92b32c789" dependencies = [ "futures-intrusive", "wgpu 30.0.1", diff --git a/Cargo.toml b/Cargo.toml index fb5ea3de5c..3a638dfa03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -289,14 +289,15 @@ wgpu = { workspace = true } wgpu_context = { workspace = true } rustc-hash = { workspace = true } -# [patch.crates-io] -# anyrender = { path = "../anyrender/crates/anyrender" } -# anyrender_skia = { path = "../anyrender/crates/anyrender_skia" } -# anyrender_vello = { path = "../anyrender/crates/anyrender_vello" } -# anyrender_vello_cpu = { path = "../anyrender/crates/anyrender_vello_cpu" } -# anyrender_vello_hybrid = { path = "../anyrender/crates/anyrender_vello_hybrid" } -# anyrender_svg = { path = "../anyrender/crates/anyrender_svg" } -# wgpu_context = { path = "../anyrender/crates/wgpu_context" } +[patch.crates-io] +anyrender = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1791068178-box-shadow-api" } +anyrender_serialize = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1791068178-box-shadow-api" } +anyrender_skia = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1791068178-box-shadow-api" } +anyrender_vello = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1791068178-box-shadow-api" } +anyrender_vello_cpu = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1791068178-box-shadow-api" } +anyrender_vello_hybrid = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1791068178-box-shadow-api" } +anyrender_svg = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1791068178-box-shadow-api" } +wgpu_context = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1791068178-box-shadow-api" } # vello = { path = "../vello/vello" } # vello_cpu = { path = "../vello/sparse_strips/vello_cpu" } diff --git a/packages/blitz-paint/src/kurbo_css/css_box.rs b/packages/blitz-paint/src/kurbo_css/css_box.rs index 321077fe00..dc21a90c1b 100644 --- a/packages/blitz-paint/src/kurbo_css/css_box.rs +++ b/packages/blitz-paint/src/kurbo_css/css_box.rs @@ -1,3 +1,4 @@ +use anyrender::NonUniformRoundedRect; use kurbo::{Arc, BezPath, Insets, PathEl, Point, Rect, Shape as _, Vec2}; use std::{f64::consts::FRAC_PI_2, f64::consts::PI}; @@ -209,6 +210,47 @@ impl CssBox { path } + /// The frame's border box as a shape that backends can recognise as a (rounded) rect + pub fn border_box_shape(&self) -> NonUniformRoundedRect { + self.box_shape(CssBoxKind::BorderBox) + } + + /// The frame's padding box as a shape that backends can recognise as a (rounded) rect + pub fn padding_box_shape(&self) -> NonUniformRoundedRect { + self.box_shape(CssBoxKind::PaddingBox) + } + + /// The frame's content box as a shape that backends can recognise as a (rounded) rect + pub fn content_box_shape(&self) -> NonUniformRoundedRect { + self.box_shape(CssBoxKind::ContentBox) + } + + fn box_shape(&self, kind: CssBoxKind) -> NonUniformRoundedRect { + use Corner::*; + let rect = match kind { + CssBoxKind::OutlineBox => self.outline_box, + CssBoxKind::BorderBox => self.border_box, + CssBoxKind::PaddingBox => self.padding_box, + CssBoxKind::ContentBox => self.content_box, + }; + let radii = |corner| { + if self.is_sharp(corner, kind) { + Vec2::ZERO + } else { + self.ellipse(corner, kind).1 + } + }; + NonUniformRoundedRect::new( + rect, + anyrender::NonUniformRoundedRectRadii { + top_left: radii(TopLeft), + top_right: radii(TopRight), + bottom_right: radii(BottomRight), + bottom_left: radii(BottomLeft), + }, + ) + } + /// Construct a bezpath drawing the frame border pub fn border_box_path(&self) -> BezPath { let mut path = BezPath::new(); @@ -286,42 +328,6 @@ impl CssBox { } } - /// Construct a bezpath drawing the frame - pub fn shadow_clip(&self, shadow_rect: Rect) -> BezPath { - let mut path = BezPath::new(); - self.shadow_clip_shape(&mut path, shadow_rect); - path - } - - fn shadow_clip_shape(&self, path: &mut BezPath, shadow_rect: Rect) { - use Corner::*; - - for corner in [TopLeft, TopRight, BottomRight, BottomLeft] { - path.insert_point(self.shadow_clip_corner(corner, shadow_rect)); - } - - if self.is_sharp(TopLeft, CssBoxKind::BorderBox) { - path.move_to(self.corner(TopLeft, CssBoxKind::BorderBox)); - } else { - const TOLERANCE: f64 = 0.1; - let arc = self.corner_arc(TopLeft, CssBoxKind::BorderBox, Direction::Anticlockwise); - let elements = arc.path_elements(TOLERANCE); - path.extend(elements); - } - - for corner in [/*TopLeft, */ BottomLeft, BottomRight, TopRight] { - if self.is_sharp(corner, CssBoxKind::BorderBox) { - path.insert_point(self.corner(corner, CssBoxKind::BorderBox)); - } else { - path.insert_arc(self.corner_arc( - corner, - CssBoxKind::BorderBox, - Direction::Anticlockwise, - )); - } - } - } - fn corner(&self, corner: Corner, css_box: CssBoxKind) -> Point { let Rect { x0, y0, x1, y1 } = match css_box { CssBoxKind::OutlineBox => self.outline_box, @@ -337,17 +343,6 @@ impl CssBox { } } - fn shadow_clip_corner(&self, corner: Corner, shadow_rect: Rect) -> Point { - let (x, y) = match corner { - Corner::TopLeft => (shadow_rect.x0, shadow_rect.y0), - Corner::TopRight => (shadow_rect.x1, shadow_rect.y0), - Corner::BottomRight => (shadow_rect.x1, shadow_rect.y1), - Corner::BottomLeft => (shadow_rect.x0, shadow_rect.y1), - }; - - Point { x, y } - } - /// Check if the corner width is smaller than the radius. /// If it is, we need to fill in the gap with an arc fn corner_needs_infill(&self, corner: Corner) -> bool { diff --git a/packages/blitz-paint/src/kurbo_css/non_uniform_radii.rs b/packages/blitz-paint/src/kurbo_css/non_uniform_radii.rs index ac8a4bcd16..1fb80aa77a 100644 --- a/packages/blitz-paint/src/kurbo_css/non_uniform_radii.rs +++ b/packages/blitz-paint/src/kurbo_css/non_uniform_radii.rs @@ -23,20 +23,6 @@ pub struct NonUniformRoundedRectRadii { pub bottom_left: Vec2, } -impl NonUniformRoundedRectRadii { - pub fn average(&self) -> f64 { - (self.top_left.x - + self.top_left.y - + self.top_right.x - + self.top_right.y - + self.bottom_left.x - + self.bottom_left.y - + self.bottom_right.x - + self.bottom_right.y) - / 8.0 - } -} - impl Mul for NonUniformRoundedRectRadii { type Output = Self; diff --git a/packages/blitz-paint/src/render.rs b/packages/blitz-paint/src/render.rs index c91d00b98c..d2d376b4f1 100644 --- a/packages/blitz-paint/src/render.rs +++ b/packages/blitz-paint/src/render.rs @@ -535,9 +535,9 @@ impl<'dom, 'a> BlitzDomPainter<'dom, 'a> { // TODO: allow layers with opacity to be unclipped (overflow: visible) let clip = if is_text_input { - &cx.frame.content_box_path() + &cx.frame.content_box_shape() } else { - &cx.frame.padding_box_path() + &cx.frame.padding_box_shape() }; // Clip layer if box requires clipping. Opacity set to 1.0 diff --git a/packages/blitz-paint/src/render/background.rs b/packages/blitz-paint/src/render/background.rs index ba7e5b072d..4ee4361886 100644 --- a/packages/blitz-paint/src/render/background.rs +++ b/packages/blitz-paint/src/render/background.rs @@ -1,10 +1,10 @@ use super::{ElementCx, to_image_quality, to_peniko_image}; use crate::color::{Color, ToColorColor}; use crate::gradient::to_peniko_gradient; -use anyrender::PaintScene; +use anyrender::{NonUniformRoundedRect, PaintScene}; use blitz_dom::NodeId; use blitz_dom::node::{ImageData, ImageResourceData, SpecialElementData}; -use kurbo::{self, Affine, BezPath, Point, Rect, Shape, Size, Vec2}; +use kurbo::{self, Affine, Point, Rect, Shape, Size, Vec2}; use peniko::{self, Fill}; use style::{ computed_values::border_collapse::T as BorderCollapse, @@ -169,11 +169,11 @@ impl ElementCx<'_, '_> { } /// The path of the given CSS box model box for this element - pub(super) fn box_path(&self, css_box: BoxModelBox) -> BezPath { + pub(super) fn box_path(&self, css_box: BoxModelBox) -> NonUniformRoundedRect { match css_box { - BoxModelBox::BorderBox => self.frame.border_box_path(), - BoxModelBox::PaddingBox => self.frame.padding_box_path(), - BoxModelBox::ContentBox => self.frame.content_box_path(), + BoxModelBox::BorderBox => self.frame.border_box_shape(), + BoxModelBox::PaddingBox => self.frame.padding_box_shape(), + BoxModelBox::ContentBox => self.frame.content_box_shape(), } } @@ -330,7 +330,7 @@ impl ElementCx<'_, '_> { } } - fn draw_solid_bg(&self, scene: &mut impl PaintScene, shape: &BezPath) { + fn draw_solid_bg(&self, scene: &mut impl PaintScene, shape: &impl Shape) { let current_color = self.style.clone_color(); let background_color = &self.style.get_background().background_color; let bg_color = background_color diff --git a/packages/blitz-paint/src/render/box_shadow.rs b/packages/blitz-paint/src/render/box_shadow.rs index 0e728204ad..e6bcf6bbff 100644 --- a/packages/blitz-paint/src/render/box_shadow.rs +++ b/packages/blitz-paint/src/render/box_shadow.rs @@ -1,14 +1,13 @@ use super::ElementCx; use crate::color::{Color, ToColorColor as _}; -use anyrender::PaintScene; -use kurbo::{Rect, Vec2}; -use peniko::{Compose, Fill, Mix}; +use anyrender::{BoxShadowKind, NonUniformRoundedRect, PaintScene}; +use kurbo::Vec2; +use style::values::computed::BoxShadow; impl ElementCx<'_, '_> { pub(super) fn draw_outset_box_shadow(&self, scene: &mut impl PaintScene) { let box_shadow = &self.style.get_effects().box_shadow.0; - let has_outset_shadow = box_shadow.iter().any(|s| !s.inset); - if !has_outset_shadow { + if !box_shadow.iter().any(|s| !s.inset) { return; } @@ -20,138 +19,67 @@ impl ElementCx<'_, '_> { .background_color .resolve_to_absolute(¤t_color) .as_srgb_color(); + // Only paint the shadow outside of the border box if it could otherwise be seen + // through the element let bg_is_opaque = bg_color.components[3] >= 1.0; - let needs_clip = opacity < 1.0 || !bg_is_opaque; - - let max_shadow_rect = box_shadow.iter().fold(Rect::ZERO, |prev, shadow| { - let x = shadow.base.horizontal.px() as f64 * self.scale; - let y = shadow.base.vertical.px() as f64 * self.scale; - let blur = shadow.base.blur.px() as f64 * self.scale; - let spread = shadow.spread.px() as f64 * self.scale; - let offset = spread + blur * 2.5; - - let rect = self.frame.border_box.inflate(offset, offset) + Vec2::new(x, y); - - prev.union(rect) - }); - - self.context.layer_manager.maybe_with_layer( - scene, - needs_clip, - 1.0, - self.transform, - &self.frame.shadow_clip(max_shadow_rect), - None, - None, - |scene| { - for shadow in box_shadow.iter().filter(|s| !s.inset).rev() { - let shadow_color = shadow - .base - .color - .resolve_to_absolute(¤t_color) - .as_srgb_color(); - - let alpha = shadow_color.components[3]; - if alpha != 0.0 { - let transform = self.transform.then_translate(Vec2 { - x: shadow.base.horizontal.px() as f64 * self.scale, - y: shadow.base.vertical.px() as f64 * self.scale, - }); - - let spread = shadow.spread.px() as f64 * self.scale; - let blur = shadow.base.blur.px() as f64; - - // Without blur or spread the shadow is exactly the element's shape, - // so fill the border box path directly (preserving individual radii) - if blur == 0.0 && spread == 0.0 { - scene.fill( - Fill::NonZero, - transform, - shadow_color, - None, - &self.frame.border_box_path(), - ); - continue; - } - - // TODO draw shadows with matching individual radii instead of averaging - let radius = self.frame.border_radii.average(); - let rect = self.frame.border_box.inflate(spread, spread); - - // Fill the color - scene.draw_box_shadow(transform, rect, shadow_color, radius, blur); - } - } - }, - ) + let clip_to_box = opacity < 1.0 || !bg_is_opaque; + + let border_box = self.frame.border_box_shape(); + for shadow in box_shadow.iter().filter(|s| !s.inset).rev() { + self.draw_box_shadow( + scene, + shadow, + &border_box, + BoxShadowKind::Outset { clip_to_box }, + ); + } } pub(super) fn draw_inset_box_shadow(&self, scene: &mut impl PaintScene) { - let current_color = self.style.clone_color(); let box_shadow = &self.style.get_effects().box_shadow.0; - let has_inset_shadow = box_shadow.iter().any(|s| s.inset); - if !has_inset_shadow { + if !box_shadow.iter().any(|s| s.inset) { return; } - let padding_box = self.frame.padding_box_path(); - - for shadow in box_shadow.iter().filter(|s| s.inset) { - let shadow_color = shadow - .base - .color - .resolve_to_absolute(¤t_color) - .as_srgb_color(); - if shadow_color == Color::TRANSPARENT { - return; - } - - // TODO draw shadows with matching individual radii instead of averaging - let radius = self.frame.border_radii.average(); - let transform = self.transform.then_translate(Vec2 { - x: shadow.base.horizontal.px() as f64, - y: shadow.base.vertical.px() as f64, - }); + let padding_box = self.frame.padding_box_shape(); + for shadow in box_shadow.iter().filter(|s| s.inset).rev() { + self.draw_box_shadow(scene, shadow, &padding_box, BoxShadowKind::Inset); + } + } - scene.push_layer( - Fill::NonZero, - Mix::Normal, - 1.0, - self.transform, - &padding_box, - None, - None, - ); - scene.fill( - Fill::NonZero, - self.transform, - shadow_color, - None, - &padding_box, - ); + fn draw_box_shadow( + &self, + scene: &mut impl PaintScene, + shadow: &BoxShadow, + box_shape: &NonUniformRoundedRect, + kind: BoxShadowKind, + ) { + let shadow_color = shadow + .base + .color + .resolve_to_absolute(&self.style.clone_color()) + .as_srgb_color(); + if shadow_color.components[3] == 0.0 { + return; + } + let shadow_color: Color = shadow_color; - scene.push_layer( - Fill::NonZero, - Compose::DestOut, - 1.0, - self.transform, - &padding_box, - None, - None, - ); - // The unshadowed area is the padding box shrunk by the spread radius - let spread = shadow.spread.px() as f64 * self.scale; - let hole = self.frame.padding_box.inflate(-spread, -spread); - scene.draw_box_shadow( - transform, - hole, - Color::WHITE, - radius, - shadow.base.blur.px() as f64 * self.scale, - ); + let offset = Vec2::new( + shadow.base.horizontal.px() as f64, + shadow.base.vertical.px() as f64, + ) * self.scale; + let spread = shadow.spread.px() as f64 * self.scale; + // The standard deviation of a CSS shadow's blur is half of its blur radius + let std_dev = shadow.base.blur.px() as f64 * self.scale / 2.0; - scene.pop_layer(); - scene.pop_layer(); - } + scene.draw_box_shadow( + self.transform, + box_shape, + offset, + spread, + std_dev, + shadow_color, + kind, + ); } } diff --git a/packages/blitz-paint/src/render/mask.rs b/packages/blitz-paint/src/render/mask.rs index 81f065fd3e..1760f6eb11 100644 --- a/packages/blitz-paint/src/render/mask.rs +++ b/packages/blitz-paint/src/render/mask.rs @@ -60,7 +60,7 @@ impl ElementCx<'_, '_> { Mix::Normal, ALMOST_OPAQUE, self.transform, - &self.frame.border_box_path(), + &self.frame.border_box_shape(), None, None, ); @@ -80,7 +80,7 @@ impl ElementCx<'_, '_> { BlendMode::new(Mix::Normal, Compose::DestIn), 1.0, self.transform, - &self.frame.border_box_path(), + &self.frame.border_box_shape(), None, None, );