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
This commit is contained in:
Simon Hausmann 2021-01-25 12:08:29 +01:00
parent 0cb51a986f
commit 628e6fdb38
8 changed files with 171 additions and 12 deletions

View file

@ -45,6 +45,7 @@ extern const cbindgen_private::ItemVTable FlickableVTable;
extern const cbindgen_private::ItemVTable WindowVTable; extern const cbindgen_private::ItemVTable WindowVTable;
extern const cbindgen_private::ItemVTable TextInputVTable; extern const cbindgen_private::ItemVTable TextInputVTable;
extern const cbindgen_private::ItemVTable ClipVTable; extern const cbindgen_private::ItemVTable ClipVTable;
extern const cbindgen_private::ItemVTable BoxShadowVTable;
extern const cbindgen_private::ItemVTable NativeButtonVTable; extern const cbindgen_private::ItemVTable NativeButtonVTable;
extern const cbindgen_private::ItemVTable NativeCheckBoxVTable; extern const cbindgen_private::ItemVTable NativeCheckBoxVTable;
@ -151,6 +152,7 @@ using cbindgen_private::Text;
using cbindgen_private::TextInput; using cbindgen_private::TextInput;
using cbindgen_private::TouchArea; using cbindgen_private::TouchArea;
using cbindgen_private::Window; using cbindgen_private::Window;
using cbindgen_private::BoxShadow;
using cbindgen_private::NativeButton; using cbindgen_private::NativeButton;
using cbindgen_private::NativeCheckBox; using cbindgen_private::NativeCheckBox;

View file

@ -132,6 +132,18 @@ export Window := _ {
property <string> title: "SixtyFPS Window"; property <string> title: "SixtyFPS Window";
} }
export BoxShadow := _ {
property <length> x;
property <length> y;
property <length> width;
property <length> height;
property <length> radius;
property <length> offset_x;
property <length> offset_y;
property <color> color;
property <length> blur;
}
export TextInput := _ { export TextInput := _ {
property <string> text; property <string> text;
property <string> font_family; property <string> font_family;

View file

@ -106,6 +106,7 @@ pub trait ItemRenderer {
fn draw_text(&mut self, pos: Point, text: Pin<&Text>); fn draw_text(&mut self, pos: Point, text: Pin<&Text>);
fn draw_text_input(&mut self, pos: Point, text_input: Pin<&TextInput>); fn draw_text_input(&mut self, pos: Point, text_input: Pin<&TextInput>);
fn draw_path(&mut self, pos: Point, path: Pin<&Path>); 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 combine_clip(&mut self, pos: Point, clip: Pin<&Clip>);
fn save_state(&mut self); fn save_state(&mut self);
fn restore_state(&mut self); fn restore_state(&mut self);

View file

@ -769,6 +769,71 @@ ItemVTable_static! {
pub static WindowVTable for Window 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<f32>,
pub y: Property<f32>,
pub width: Property<f32>,
pub height: Property<f32>,
pub radius: Property<f32>,
// Shadow specific properties
pub offset_x: Property<f32>,
pub offset_y: Property<f32>,
pub color: Property<Color>,
pub blur: Property<f32>,
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, CachedRenderingData> =
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
}
ItemVTable_static! {
/// The VTable for `BoxShadow`
#[no_mangle]
pub static BoxShadowVTable for BoxShadow
}
ItemVTable_static! { ItemVTable_static! {
/// The VTable for `Text` /// The VTable for `Text`
#[no_mangle] #[no_mangle]

View file

@ -489,6 +489,7 @@ fn generate_component<'id>(
rtti_for::<Window>(), rtti_for::<Window>(),
rtti_for::<TextInput>(), rtti_for::<TextInput>(),
rtti_for::<Clip>(), rtti_for::<Clip>(),
rtti_for::<BoxShadow>(),
] ]
.iter() .iter()
.cloned(), .cloned(),

View file

@ -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<f32, euclid::UnknownUnit> = 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<f32, euclid::UnknownUnit> = 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<f32, euclid::UnknownUnit> = 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>) { 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()); let clip_rect = clip.geometry().translate([pos.x, pos.y].into());
self.shared_data.canvas.borrow_mut().intersect_scissor( self.shared_data.canvas.borrow_mut().intersect_scissor(
@ -943,6 +1016,10 @@ impl ItemRenderer for GLItemRenderer {
self.shared_data.canvas.borrow_mut().restore(); self.shared_data.canvas.borrow_mut().restore();
} }
fn scale_factor(&self) -> f32 {
self.scale_factor
}
fn draw_cached_pixmap( fn draw_cached_pixmap(
&mut self, &mut self,
item_cache: &CachedRenderingData, item_cache: &CachedRenderingData,
@ -981,10 +1058,6 @@ impl ItemRenderer for GLItemRenderer {
canvas.fill_path(&mut path, fill_paint); 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 { fn as_any(&mut self) -> &mut dyn std::any::Any {
self self
} }

View file

@ -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>) { fn combine_clip(&mut self, pos: Point, clip: Pin<&items::Clip>) {
let clip_rect: qttypes::QRectF = get_geometry!(pos, items::Clip, clip); let clip_rect: qttypes::QRectF = get_geometry!(pos, items::Clip, clip);
let painter: &mut QPainter = &mut *self.painter; 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 { fn scale_factor(&self) -> f32 {
return 1.; return 1.;
/* cpp! { unsafe [painter as "QPainter*"] -> f32 as "float" { /* 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 { fn as_any(&mut self) -> &mut dyn std::any::Any {
self.painter self.painter
} }

View file

@ -68,6 +68,7 @@ fn gen_corelib(include_dir: &Path) -> anyhow::Result<()> {
"Window", "Window",
"TextInput", "TextInput",
"Clip", "Clip",
"BoxShadow",
] ]
.iter() .iter()
.map(|x| x.to_string()) .map(|x| x.to_string())