mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 06:11:16 +00:00
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:
parent
0cb51a986f
commit
628e6fdb38
8 changed files with 171 additions and 12 deletions
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue