From 246ba211c32b8cae550c0fb0f1c449365451d71c Mon Sep 17 00:00:00 2001 From: Dongdong Zhou Date: Fri, 18 Aug 2023 20:23:49 +0100 Subject: [PATCH] add box shadow --- examples/counter/src/main.rs | 13 +++++++--- renderer/src/lib.rs | 2 +- src/app_handle.rs | 13 +++++++++- src/context.rs | 18 ++++++++++++-- src/id.rs | 6 +++++ src/renderer.rs | 3 ++- src/style.rs | 47 ++++++++++++++++++++++++++++++++++++ src/view.rs | 45 +++++++++++++++++++++++++--------- src/views/decorator.rs | 8 +++++- src/views/scroll.rs | 23 ++++++++++++++++-- src/views/text_input.rs | 3 ++- vger/Cargo.toml | 2 +- vger/src/lib.rs | 6 +++-- 13 files changed, 163 insertions(+), 26 deletions(-) diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index 683c0c8e..796fc3cf 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -15,7 +15,13 @@ fn app_view() -> impl View { stack(|| { ( label(|| "Increment".to_string()) - .style(|| Style::BASE.border(1.0).border_radius(10.0).padding_px(10.0)) + .style(|| { + Style::BASE + .border_radius(10.0) + .padding_px(10.0) + .background(Color::WHITE) + .box_shadow_blur(5.0) + }) .on_click({ move |_| { set_counter.update(|value| *value += 1); @@ -39,7 +45,8 @@ fn app_view() -> impl View { }) .style(|| { Style::BASE - .border(1.0) + .box_shadow_blur(5.0) + .background(Color::WHITE) .border_radius(10.0) .padding_px(10.0) .margin_left_px(10.0) @@ -57,7 +64,7 @@ fn app_view() -> impl View { .disabled(move || counter.get() == 0) .style(|| { Style::BASE - .border(1.0) + .box_shadow_blur(5.0) .border_radius(10.0) .padding_px(10.0) .margin_left_px(10.0) diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 2ee14e7a..4a9f95f4 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -30,7 +30,7 @@ pub trait Renderer { /// Fill a [`Shape`], using the [non-zero fill rule]. /// /// [non-zero fill rule]: https://en.wikipedia.org/wiki/Nonzero-rule - fn fill<'b>(&mut self, path: &impl Shape, brush: impl Into>); + fn fill<'b>(&mut self, path: &impl Shape, brush: impl Into>, blur_radius: f64); /// Draw a [`TextLayout`]. /// diff --git a/src/app_handle.rs b/src/app_handle.rs index 9dc5fc78..8e5269e8 100644 --- a/src/app_handle.rs +++ b/src/app_handle.rs @@ -4,6 +4,7 @@ use std::{any::Any, collections::HashMap}; use crate::action::exec_after; use crate::animate::AnimValue; +use crate::context::MoveListener; use crate::id::WindowId; use crate::view::{view_debug_tree, view_tab_navigation}; use crate::window::WINDOWS; @@ -162,6 +163,10 @@ pub enum UpdateMessage { id: Id, action: Box, }, + MoveListener { + id: Id, + action: Box, + }, CleanupListener { id: Id, action: Box, @@ -546,11 +551,17 @@ impl AppHandle { UpdateMessage::ResizeListener { id, action } => { let state = cx.app_state.view_state(id); state.resize_listener = Some(ResizeListener { - window_origin: Point::ZERO, rect: Rect::ZERO, callback: action, }); } + UpdateMessage::MoveListener { id, action } => { + let state = cx.app_state.view_state(id); + state.move_listener = Some(MoveListener { + window_origin: Point::ZERO, + callback: action, + }); + } UpdateMessage::CleanupListener { id, action } => { let state = cx.app_state.view_state(id); state.cleanup_listener = Some(action); diff --git a/src/context.rs b/src/context.rs index 5df096cf..5a9841ce 100644 --- a/src/context.rs +++ b/src/context.rs @@ -64,15 +64,20 @@ impl ViewContextStore { } pub type EventCallback = dyn Fn(&Event) -> bool; -pub type ResizeCallback = dyn Fn(Point, Rect); +pub type ResizeCallback = dyn Fn(Rect); pub type MenuCallback = dyn Fn() -> Menu; pub(crate) struct ResizeListener { - pub(crate) window_origin: Point, pub(crate) rect: Rect, pub(crate) callback: Box, } +/// The listener when the view is got moved to a different position in the window +pub(crate) struct MoveListener { + pub(crate) window_origin: Point, + pub(crate) callback: Box, +} + pub struct ViewState { pub(crate) node: Node, pub(crate) children_nodes: Vec, @@ -95,6 +100,7 @@ pub struct ViewState { pub(crate) context_menu: Option>, pub(crate) popout_menu: Option>, pub(crate) resize_listener: Option, + pub(crate) move_listener: Option, pub(crate) cleanup_listener: Option>, pub(crate) last_pointer_down: Option, } @@ -123,6 +129,7 @@ impl ViewState { context_menu: None, popout_menu: None, resize_listener: None, + move_listener: None, cleanup_listener: None, last_pointer_down: None, } @@ -841,6 +848,13 @@ impl<'a> LayoutCx<'a> { .get_mut(&id) .and_then(|s| s.resize_listener.as_mut()) } + + pub(crate) fn get_move_listener(&mut self, id: Id) -> Option<&mut MoveListener> { + self.app_state + .view_states + .get_mut(&id) + .and_then(|s| s.move_listener.as_mut()) + } } pub struct PaintCx<'a> { diff --git a/src/id.rs b/src/id.rs index 55e77281..b78e8c50 100644 --- a/src/id.rs +++ b/src/id.rs @@ -10,6 +10,8 @@ use std::{any::Any, cell::RefCell, collections::HashMap, num::NonZeroU64}; +use glazier::kurbo::Point; + use crate::{ animate::Animation, app_handle::{StyleSelector, UpdateMessage, DEFERRED_UPDATE_MESSAGES, UPDATE_MESSAGES}, @@ -199,6 +201,10 @@ impl Id { self.add_update_message(UpdateMessage::ResizeListener { id: *self, action }); } + pub fn update_move_listener(&self, action: Box) { + self.add_update_message(UpdateMessage::MoveListener { id: *self, action }); + } + pub fn update_cleanup_listener(&self, action: Box) { self.add_update_message(UpdateMessage::CleanupListener { id: *self, action }); } diff --git a/src/renderer.rs b/src/renderer.rs index a55b709c..fc72381b 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -70,10 +70,11 @@ impl floem_renderer::Renderer for Renderer { &mut self, path: &impl glazier::kurbo::Shape, brush: impl Into>, + blur_radius: f64, ) { match self { Renderer::Vger(v) => { - v.fill(path, brush); + v.fill(path, brush, blur_radius); } } } diff --git a/src/style.rs b/src/style.rs index 35475973..b8d473c0 100644 --- a/src/style.rs +++ b/src/style.rs @@ -51,6 +51,12 @@ pub enum CursorStyle { Text, } +#[derive(Debug, Clone, Copy)] +pub struct BoxShadow { + pub blur_radius: f64, + pub color: Color, +} + /// The value for a [`Style`] property #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StyleValue { @@ -86,6 +92,14 @@ impl StyleValue { Self::Base => f(), } } + + pub fn as_mut(&mut self) -> Option<&mut T> { + match self { + Self::Val(x) => Some(x), + Self::Unset => None, + Self::Base => None, + } + } } impl Default for StyleValue { @@ -250,6 +264,7 @@ define_styles!( cursor nocb: Option = None, color nocb: Option = None, background nocb: Option = None, + box_shadow nocb: Option = None, font_size nocb: Option = None, font_family nocb: Option = None, font_weight nocb: Option = None, @@ -565,6 +580,38 @@ impl Style { self } + pub fn box_shadow_blur(mut self, blur_radius: f64) -> Self { + if let Some(box_shadow) = self.box_shadow.as_mut() { + if let Some(box_shadow) = box_shadow.as_mut() { + box_shadow.blur_radius = blur_radius; + return self; + } + } + + self.box_shadow = Some(BoxShadow { + blur_radius, + color: Color::BLACK, + }) + .into(); + self + } + + pub fn box_shadow_color(mut self, color: Color) -> Self { + if let Some(box_shadow) = self.box_shadow.as_mut() { + if let Some(box_shadow) = box_shadow.as_mut() { + box_shadow.color = color; + return self; + } + } + + self.box_shadow = Some(BoxShadow { + blur_radius: 0.0, + color, + }) + .into(); + self + } + pub fn font_size(mut self, size: impl Into>) -> Self { self.font_size = size.into().map(Some); self diff --git a/src/view.rs b/src/view.rs index 2b41f279..39c5d727 100644 --- a/src/view.rs +++ b/src/view.rs @@ -286,10 +286,16 @@ pub trait View { if let Some(resize) = cx.get_resize_listener(self.id()) { let new_rect = size.to_rect().with_origin(origin); - if new_rect != resize.rect || window_origin != resize.window_origin { + if new_rect != resize.rect { resize.rect = new_rect; - resize.window_origin = window_origin; - (*resize.callback)(window_origin, new_rect); + (*resize.callback)(new_rect); + } + } + + if let Some(listener) = cx.get_move_listener(self.id()) { + if window_origin != listener.window_origin { + listener.window_origin = window_origin; + (*listener.callback)(window_origin); } } @@ -734,11 +740,6 @@ pub trait View { } fn paint_bg(cx: &mut PaintCx, style: &ComputedStyle, size: Size) { - let bg = match style.background { - Some(color) => color, - None => return, - }; - let radius = style.border_radius; if radius > 0.0 { let rect = size.to_rect(); @@ -747,13 +748,35 @@ fn paint_bg(cx: &mut PaintCx, style: &ComputedStyle, size: Size) { if width > 0.0 && height > 0.0 && radius as f64 > width.max(height) / 2.0 { let radius = width.max(height) / 2.0; let circle = Circle::new(rect.center(), radius); - cx.fill(&circle, bg); + let bg = match style.background { + Some(color) => color, + None => return, + }; + cx.fill(&circle, bg, 0.0); } else { let rect = rect.to_rounded_rect(radius as f64); - cx.fill(&rect, bg); + if let Some(shadow) = style.box_shadow.as_ref() { + if shadow.blur_radius > 0.0 { + cx.fill(&rect, shadow.color, shadow.blur_radius); + } + } + let bg = match style.background { + Some(color) => color, + None => return, + }; + cx.fill(&rect, bg, 0.0); } } else { - cx.fill(&size.to_rect(), bg); + if let Some(shadow) = style.box_shadow.as_ref() { + if shadow.blur_radius > 0.0 { + cx.fill(&size.to_rect(), shadow.color, shadow.blur_radius); + } + } + let bg = match style.background { + Some(color) => color, + None => return, + }; + cx.fill(&size.to_rect(), bg, 0.0); } } diff --git a/src/views/decorator.rs b/src/views/decorator.rs index 35e57521..b5c9ed7b 100644 --- a/src/views/decorator.rs +++ b/src/views/decorator.rs @@ -145,12 +145,18 @@ pub trait Decorators: View + Sized { self } - fn on_resize(self, action: impl Fn(Point, Rect) + 'static) -> Self { + fn on_resize(self, action: impl Fn(Rect) + 'static) -> Self { let id = self.id(); id.update_resize_listener(Box::new(action)); self } + fn on_move(self, action: impl Fn(Point) + 'static) -> Self { + let id = self.id(); + id.update_move_listener(Box::new(action)); + self + } + fn on_cleanup(self, action: impl Fn() + 'static) -> Self { let id = self.id(); id.update_cleanup_listener(Box::new(action)); diff --git a/src/views/scroll.rs b/src/views/scroll.rs index 730fb458..855f2105 100644 --- a/src/views/scroll.rs +++ b/src/views/scroll.rs @@ -26,6 +26,7 @@ enum ScrollState { ScrollBarColor(Color), HiddenBar(bool), PropagatePointerWheel(bool), + VerticalScrollAsHorizontal(bool), } /// Minimum length for any scrollbar to be when measured on that @@ -59,6 +60,7 @@ pub struct Scroll { virtual_node: Option, hide_bar: bool, propagate_pointer_wheel: bool, + vertical_scroll_as_horizontal: bool, scroll_bar_color: Color, } @@ -76,6 +78,7 @@ pub fn scroll(child: impl FnOnce() -> V) -> Scroll { virtual_node: None, hide_bar: false, propagate_pointer_wheel: false, + vertical_scroll_as_horizontal: false, // 179 is 70% of 255 so a 70% alpha factor is the default scroll_bar_color: Color::rgba8(0, 0, 0, 179), } @@ -144,6 +147,14 @@ impl Scroll { self } + pub fn vertical_scroll_as_horizontal(self, value: impl Fn() -> bool + 'static) -> Self { + let id = self.id; + create_effect(move |_| { + id.update_state(ScrollState::VerticalScrollAsHorizontal(value()), false); + }); + self + } + fn scroll_delta(&mut self, app_state: &mut AppState, delta: Vec2) { let new_origin = self.child_viewport.origin() + delta; self.clamp_child_viewport(app_state, self.child_viewport.with_origin(new_origin)); @@ -304,7 +315,7 @@ impl Scroll { let color = self.scroll_bar_color; if let Some(bounds) = self.calc_vertical_bar_bounds(cx.app_state) { let rect = (bounds - scroll_offset).inset(-edge_width / 2.0); - cx.fill(&rect, color); + cx.fill(&rect, color, 0.0); if edge_width > 0.0 { cx.stroke(&rect, color, edge_width); } @@ -313,7 +324,7 @@ impl Scroll { // Horizontal bar if let Some(bounds) = self.calc_horizontal_bar_bounds(cx.app_state) { let rect = (bounds - scroll_offset).inset(-edge_width / 2.0); - cx.fill(&rect, color); + cx.fill(&rect, color, 0.0); if edge_width > 0.0 { cx.stroke(&rect, color, edge_width); } @@ -523,6 +534,9 @@ impl View for Scroll { ScrollState::PropagatePointerWheel(value) => { self.propagate_pointer_wheel = value; } + ScrollState::VerticalScrollAsHorizontal(value) => { + self.vertical_scroll_as_horizontal = value; + } } cx.request_layout(self.id()); ChangeFlags::LAYOUT @@ -685,6 +699,11 @@ impl View for Scroll { } else { Vec2::ZERO }; + let delta = if self.vertical_scroll_as_horizontal && delta.x == 0.0 && delta.y != 0.0 { + Vec2::new(delta.y, delta.x) + } else { + delta + }; self.clamp_child_viewport(cx.app_state, self.child_viewport + delta); return !self.propagate_pointer_wheel; } diff --git a/src/views/text_input.rs b/src/views/text_input.rs index 0e31bed5..b46ddeb1 100644 --- a/src/views/text_input.rs +++ b/src/views/text_input.rs @@ -713,13 +713,14 @@ impl View for TextInput { if is_cursor_visible { let cursor_rect = self.get_cursor_rect(&node_layout); - cx.fill(&cursor_rect, cursor_color.unwrap_or(Color::BLACK)); + cx.fill(&cursor_rect, cursor_color.unwrap_or(Color::BLACK), 0.0); } if cx.app_state.is_focused(&self.id) { let selection_rect = self.get_selection_rect(&node_layout); cx.fill( &selection_rect, cursor_color.unwrap_or(Color::rgba8(0, 0, 0, 150)), + 0.0, ); } else { self.selection = 0..0; diff --git a/vger/Cargo.toml b/vger/Cargo.toml index 8b1500a9..33c38723 100644 --- a/vger/Cargo.toml +++ b/vger/Cargo.toml @@ -12,6 +12,6 @@ futures = "0.3.26" anyhow = "1.0.69" peniko = { git = "https://github.com/linebender/peniko", rev = "cafdac9a211a0fb2fec5656bd663d1ac770bcc81" } swash = "0.1.8" -vger = { git = "https://github.com/lapce/vger-rs", rev = "d8054007a865a734d3aa5000af3aec9d4964ead6" } +vger = { git = "https://github.com/lapce/vger-rs", rev = "952143f37d8822ef3754189734c62549025e7df5" } # vger = { path = "../../vger-rs" } floem_renderer = { path = "../renderer" } diff --git a/vger/src/lib.rs b/vger/src/lib.rs index 74ed4e6e..dfb908b2 100644 --- a/vger/src/lib.rs +++ b/vger/src/lib.rs @@ -208,18 +208,20 @@ impl Renderer for VgerRenderer { } } - fn fill<'b>(&mut self, path: &impl Shape, brush: impl Into>) { + fn fill<'b>(&mut self, path: &impl Shape, brush: impl Into>, blur_radius: f64) { let paint = match self.brush_to_paint(brush) { Some(paint) => paint, None => return, }; if let Some(rect) = path.as_rect() { - self.vger.fill_rect(self.vger_rect(rect), 0.0, paint); + self.vger + .fill_rect(self.vger_rect(rect), 0.0, paint, blur_radius as f32); } else if let Some(rect) = path.as_rounded_rect() { self.vger.fill_rect( self.vger_rect(rect.rect()), rect.radii().top_left as f32, paint, + blur_radius as f32, ); } else if let Some(circle) = path.as_circle() { self.vger.fill_circle(