From 628e6fdb3874ba82b2ed48dac95bccd2f106a41f Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Mon, 25 Jan 2021 12:08:29 +0100 Subject: [PATCH] Add a BoxShadow element This intends to provide a configurable rectangular "drop shadow". The API is modeled after CSS/HTML5 Canvas where the element can be "bound" to an existing rectangular shape (geometry and radius), the offset can be used to place the shadow and color and blur configure the shadow. The shadow's color fades into transparent. TODO (in subsequent changes): * Documentation * Qt implementation --- api/sixtyfps-cpp/include/sixtyfps.h | 2 + sixtyfps_compiler/builtins.60 | 12 +++ sixtyfps_runtime/corelib/item_rendering.rs | 1 + sixtyfps_runtime/corelib/items.rs | 65 +++++++++++++++ .../interpreter/dynamic_component.rs | 1 + sixtyfps_runtime/rendering_backends/gl/lib.rs | 81 ++++++++++++++++++- .../rendering_backends/qt/qt_window.rs | 20 +++-- tests/driver_lib/cbindgen.rs | 1 + 8 files changed, 171 insertions(+), 12 deletions(-) diff --git a/api/sixtyfps-cpp/include/sixtyfps.h b/api/sixtyfps-cpp/include/sixtyfps.h index 24bf10669..4f029f76d 100644 --- a/api/sixtyfps-cpp/include/sixtyfps.h +++ b/api/sixtyfps-cpp/include/sixtyfps.h @@ -45,6 +45,7 @@ extern const cbindgen_private::ItemVTable FlickableVTable; extern const cbindgen_private::ItemVTable WindowVTable; extern const cbindgen_private::ItemVTable TextInputVTable; extern const cbindgen_private::ItemVTable ClipVTable; +extern const cbindgen_private::ItemVTable BoxShadowVTable; extern const cbindgen_private::ItemVTable NativeButtonVTable; extern const cbindgen_private::ItemVTable NativeCheckBoxVTable; @@ -151,6 +152,7 @@ using cbindgen_private::Text; using cbindgen_private::TextInput; using cbindgen_private::TouchArea; using cbindgen_private::Window; +using cbindgen_private::BoxShadow; using cbindgen_private::NativeButton; using cbindgen_private::NativeCheckBox; diff --git a/sixtyfps_compiler/builtins.60 b/sixtyfps_compiler/builtins.60 index ad5f4ca0c..53224e047 100644 --- a/sixtyfps_compiler/builtins.60 +++ b/sixtyfps_compiler/builtins.60 @@ -132,6 +132,18 @@ export Window := _ { property title: "SixtyFPS Window"; } +export BoxShadow := _ { + property x; + property y; + property width; + property height; + property radius; + property offset_x; + property offset_y; + property color; + property blur; +} + export TextInput := _ { property text; property font_family; diff --git a/sixtyfps_runtime/corelib/item_rendering.rs b/sixtyfps_runtime/corelib/item_rendering.rs index af50a2e06..48c71d2c3 100644 --- a/sixtyfps_runtime/corelib/item_rendering.rs +++ b/sixtyfps_runtime/corelib/item_rendering.rs @@ -106,6 +106,7 @@ pub trait ItemRenderer { fn draw_text(&mut self, pos: Point, text: Pin<&Text>); fn draw_text_input(&mut self, pos: Point, text_input: Pin<&TextInput>); fn draw_path(&mut self, pos: Point, path: Pin<&Path>); + fn draw_box_shadow(&mut self, pos: Point, box_shadow: Pin<&BoxShadow>); fn combine_clip(&mut self, pos: Point, clip: Pin<&Clip>); fn save_state(&mut self); fn restore_state(&mut self); diff --git a/sixtyfps_runtime/corelib/items.rs b/sixtyfps_runtime/corelib/items.rs index 7eb58b0e7..f02dc5c8f 100644 --- a/sixtyfps_runtime/corelib/items.rs +++ b/sixtyfps_runtime/corelib/items.rs @@ -769,6 +769,71 @@ ItemVTable_static! { pub static WindowVTable for Window } +/// The implementation of the `BoxShadow` element +#[repr(C)] +#[derive(FieldOffsets, Default, SixtyFPSElement)] +#[pin] +pub struct BoxShadow { + // Rectangle properties + pub x: Property, + pub y: Property, + pub width: Property, + pub height: Property, + pub radius: Property, + // Shadow specific properties + pub offset_x: Property, + pub offset_y: Property, + pub color: Property, + pub blur: Property, + pub cached_rendering_data: CachedRenderingData, +} + +impl Item for BoxShadow { + fn init(self: Pin<&Self>, _window: &ComponentWindow) {} + + fn geometry(self: Pin<&Self>) -> Rect { + euclid::rect(self.x(), self.y(), self.width(), self.height()) + } + + fn layouting_info(self: Pin<&Self>, _window: &ComponentWindow) -> LayoutInfo { + LayoutInfo::default() + } + + fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size { + Default::default() + } + + fn input_event( + self: Pin<&Self>, + _event: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventResult { + InputEventResult::EventIgnored + } + + fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult { + KeyEventResult::EventIgnored + } + + fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {} + + fn render(self: Pin<&Self>, pos: Point, backend: &mut ItemRendererRef) { + (*backend).draw_box_shadow(pos, self) + } +} + +impl ItemConsts for BoxShadow { + const cached_rendering_data_offset: const_field_offset::FieldOffset = + Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection(); +} + +ItemVTable_static! { + /// The VTable for `BoxShadow` + #[no_mangle] + pub static BoxShadowVTable for BoxShadow +} + ItemVTable_static! { /// The VTable for `Text` #[no_mangle] diff --git a/sixtyfps_runtime/interpreter/dynamic_component.rs b/sixtyfps_runtime/interpreter/dynamic_component.rs index 99369c8a5..df834be9c 100644 --- a/sixtyfps_runtime/interpreter/dynamic_component.rs +++ b/sixtyfps_runtime/interpreter/dynamic_component.rs @@ -489,6 +489,7 @@ fn generate_component<'id>( rtti_for::(), rtti_for::(), rtti_for::(), + rtti_for::(), ] .iter() .cloned(), diff --git a/sixtyfps_runtime/rendering_backends/gl/lib.rs b/sixtyfps_runtime/rendering_backends/gl/lib.rs index e9d4df4ec..6ba4cee94 100644 --- a/sixtyfps_runtime/rendering_backends/gl/lib.rs +++ b/sixtyfps_runtime/rendering_backends/gl/lib.rs @@ -925,6 +925,79 @@ impl ItemRenderer for GLItemRenderer { }) } + fn draw_box_shadow( + &mut self, + pos: Point, + box_shadow: std::pin::Pin<&sixtyfps_corelib::items::BoxShadow>, + ) { + // TODO: cache path in item to avoid re-tesselation + + let blur = box_shadow.blur(); + + let shadow_outer_rect: euclid::Rect = euclid::rect( + box_shadow.x() + box_shadow.offset_x() - blur / 2., + box_shadow.y() + box_shadow.offset_y() - blur / 2., + box_shadow.width() + blur, + box_shadow.height() + blur, + ); + + let shadow_inner_rect: euclid::Rect = euclid::rect( + box_shadow.x() + box_shadow.offset_x() + blur / 2., + box_shadow.y() + box_shadow.offset_y() + blur / 2., + box_shadow.width() - blur, + box_shadow.height() - blur, + ); + + let shadow_fill_rect: euclid::Rect = euclid::rect( + shadow_outer_rect.min_x() + blur / 2., + shadow_outer_rect.min_y() + blur / 2., + box_shadow.width(), + box_shadow.height(), + ); + + let paint = femtovg::Paint::box_gradient( + shadow_fill_rect.min_x(), + shadow_fill_rect.min_y(), + shadow_fill_rect.width(), + shadow_fill_rect.height(), + box_shadow.radius(), + box_shadow.blur(), + box_shadow.color().into(), + Color::from_argb_u8(0, 0, 0, 0).into(), + ); + + let mut path = femtovg::Path::new(); + path.rounded_rect( + shadow_outer_rect.min_x(), + shadow_outer_rect.min_y(), + shadow_outer_rect.width(), + shadow_outer_rect.height(), + box_shadow.radius(), + ); + path.rect( + shadow_inner_rect.min_x(), + shadow_inner_rect.min_y(), + shadow_inner_rect.width(), + shadow_inner_rect.height(), + ); + path.solidity(femtovg::Solidity::Hole); + + self.shared_data.canvas.borrow_mut().save_with(|canvas| { + canvas.translate(pos.x, pos.y); + canvas.fill_path(&mut path, paint); + + let mut shadow_inner_path = femtovg::Path::new(); + shadow_inner_path.rect( + shadow_inner_rect.min_x(), + shadow_inner_rect.min_y(), + shadow_inner_rect.width(), + shadow_inner_rect.height(), + ); + let fill = femtovg::Paint::color(box_shadow.color().into()); + canvas.fill_path(&mut shadow_inner_path, fill); + }) + } + fn combine_clip(&mut self, pos: Point, clip: std::pin::Pin<&sixtyfps_corelib::items::Clip>) { let clip_rect = clip.geometry().translate([pos.x, pos.y].into()); self.shared_data.canvas.borrow_mut().intersect_scissor( @@ -943,6 +1016,10 @@ impl ItemRenderer for GLItemRenderer { self.shared_data.canvas.borrow_mut().restore(); } + fn scale_factor(&self) -> f32 { + self.scale_factor + } + fn draw_cached_pixmap( &mut self, item_cache: &CachedRenderingData, @@ -981,10 +1058,6 @@ impl ItemRenderer for GLItemRenderer { canvas.fill_path(&mut path, fill_paint); } - fn scale_factor(&self) -> f32 { - self.scale_factor - } - fn as_any(&mut self) -> &mut dyn std::any::Any { self } diff --git a/sixtyfps_runtime/rendering_backends/qt/qt_window.rs b/sixtyfps_runtime/rendering_backends/qt/qt_window.rs index 4d3a0dc24..97dde75cb 100644 --- a/sixtyfps_runtime/rendering_backends/qt/qt_window.rs +++ b/sixtyfps_runtime/rendering_backends/qt/qt_window.rs @@ -423,6 +423,10 @@ impl ItemRenderer for QtItemRenderer<'_> { }} } + fn draw_box_shadow(&mut self, _pos: Point, _box_shadow: Pin<&items::BoxShadow>) { + todo!() + } + fn combine_clip(&mut self, pos: Point, clip: Pin<&items::Clip>) { let clip_rect: qttypes::QRectF = get_geometry!(pos, items::Clip, clip); let painter: &mut QPainter = &mut *self.painter; @@ -431,6 +435,14 @@ impl ItemRenderer for QtItemRenderer<'_> { }} } + fn save_state(&mut self) { + self.painter.save_state() + } + + fn restore_state(&mut self) { + self.painter.restore_state() + } + fn scale_factor(&self) -> f32 { return 1.; /* cpp! { unsafe [painter as "QPainter*"] -> f32 as "float" { @@ -455,14 +467,6 @@ impl ItemRenderer for QtItemRenderer<'_> { }) } - fn save_state(&mut self) { - self.painter.save_state() - } - - fn restore_state(&mut self) { - self.painter.restore_state() - } - fn as_any(&mut self) -> &mut dyn std::any::Any { self.painter } diff --git a/tests/driver_lib/cbindgen.rs b/tests/driver_lib/cbindgen.rs index ecc4b2148..ee097beca 100644 --- a/tests/driver_lib/cbindgen.rs +++ b/tests/driver_lib/cbindgen.rs @@ -68,6 +68,7 @@ fn gen_corelib(include_dir: &Path) -> anyhow::Result<()> { "Window", "TextInput", "Clip", + "BoxShadow", ] .iter() .map(|x| x.to_string())