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())