Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shadow and slider #819

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
fcd5c9a
feat: add shadow support to SizedBox widget
snooowfire Jan 10, 2025
5bdf026
test: add shadow rendering tests for SizedBox widget
snooowfire Jan 10, 2025
0ab4800
fix: add missing shadow field and fix shadow rendering in SizedBox
snooowfire Jan 10, 2025
adf58c9
feat: Add shadow field to SizedBox constructors
snooowfire Jan 10, 2025
1777255
feat: add corner radius support for shadows in SizedBox widget
snooowfire Jan 10, 2025
a48fdf4
feat: Add shadow support to SizedBox view with example
snooowfire Jan 10, 2025
9ccd311
feat: Move shadow example from web to xilem/examples
snooowfire Jan 10, 2025
15d3793
feat: add shadow example to Cargo.toml
snooowfire Jan 10, 2025
a6391ef
feat: add Slider widget with configurable range, colors and accessibi…
snooowfire Jan 10, 2025
24ac807
fix: Add missing imports and implement widget methods in slider
snooowfire Jan 10, 2025
f57d801
feat: Implement child widget handling in Slider component
snooowfire Jan 10, 2025
85b0eb5
fix: Add missing imports and update paths in slider.rs
snooowfire Jan 10, 2025
773b091
fix: Add missing Affine import in Slider widget
snooowfire Jan 10, 2025
d7b6eed
feat: add Slider widget implementation to xilem
snooowfire Jan 10, 2025
69135cd
refactor: enhance slider with range, color customization, and builder…
snooowfire Jan 10, 2025
c6a5e4e
refactor: align slider implementation with Xilem view patterns
snooowfire Jan 10, 2025
3f56150
refactor: Move ViewCtx import to correct module in slider.rs
snooowfire Jan 10, 2025
a1e1fd7
refactor: align slider implementation with button's patterns and mess…
snooowfire Jan 10, 2025
1807b81
refactor: Remove unused ViewCtx import in slider.rs
snooowfire Jan 10, 2025
489d3ca
refactor: update slider's on_change callback to match button pattern
snooowfire Jan 10, 2025
d498251
refactor: Replace Box<dyn> with generic F for Slider's on_change call…
snooowfire Jan 10, 2025
9fe10b1
refactor: Simplify Slider implementation by removing State and Action…
snooowfire Jan 10, 2025
ad07796
refactor: generalize slider function to accept State and Action types
snooowfire Jan 10, 2025
01da463
refactor: make on_change a required parameter in slider constructor
snooowfire Jan 10, 2025
5ebee47
refactor: simplify slider widget implementation and add tests
snooowfire Jan 10, 2025
3610696
chore: remove shadow example from workspace
snooowfire Jan 10, 2025
5382a19
feat: add SliderValueChanged action for slider widget
snooowfire Jan 10, 2025
abbc254
feat: add shadow support to SizedBox widget
snooowfire Jan 10, 2025
43dae69
feat: add shadow example demonstrating shadow controls
snooowfire Jan 10, 2025
9b359c7
feat: update SizedBox view to support shadow properties
snooowfire Jan 10, 2025
d130631
refactor: improve Slider view implementation
snooowfire Jan 10, 2025
af4fc84
Merge commit 'd130631' into shadow
snooowfire Jan 10, 2025
d92d36b
feat: enhance slider widget with new features and improvements
Cupnfish Jan 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
.DS_Store
*.diff
8 changes: 8 additions & 0 deletions masonry/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub enum Action {
TextChanged(String),
TextEntered(String),
CheckboxChecked(bool),
SliderValueChanged(f64),
SliderEditingChanged(bool),
// FIXME - This is a huge hack
Other(Box<dyn Any + Send>),
}
Expand All @@ -28,6 +30,8 @@ impl PartialEq for Action {
(Self::TextChanged(l0), Self::TextChanged(r0)) => l0 == r0,
(Self::TextEntered(l0), Self::TextEntered(r0)) => l0 == r0,
(Self::CheckboxChecked(l0), Self::CheckboxChecked(r0)) => l0 == r0,
(Self::SliderValueChanged(l0), Self::SliderValueChanged(r0)) => l0 == r0,
(Self::SliderEditingChanged(l0), Self::SliderEditingChanged(r0)) => l0 == r0,
// FIXME
// (Self::Other(val_l), Self::Other(val_r)) => false,
_ => false,
Expand All @@ -42,6 +46,10 @@ impl std::fmt::Debug for Action {
Self::TextChanged(text) => f.debug_tuple("TextChanged").field(text).finish(),
Self::TextEntered(text) => f.debug_tuple("TextEntered").field(text).finish(),
Self::CheckboxChecked(b) => f.debug_tuple("CheckboxChecked").field(b).finish(),
Self::SliderValueChanged(b) => f.debug_tuple("SliderValueChanged").field(b).finish(),
Self::SliderEditingChanged(b) => {
f.debug_tuple("SliderEditingChanged").field(b).finish()
}
Self::Other(_) => write!(f, "Other(...)"),
}
}
Expand Down
2 changes: 2 additions & 0 deletions masonry/src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod prose;
mod root_widget;
mod scroll_bar;
mod sized_box;
mod slider;
mod spinner;
mod split;
mod text_area;
Expand All @@ -35,6 +36,7 @@ mod widget_arena;
mod zstack;

pub use self::image::Image;
pub use slider::Slider;
pub use align::Align;
pub use button::Button;
pub use checkbox::Checkbox;
Expand Down
131 changes: 130 additions & 1 deletion masonry/src/widget/sized_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
use accesskit::{Node, Role};
use smallvec::{smallvec, SmallVec};
use tracing::{trace_span, warn, Span};
use vello::kurbo::Vec2;
use vello::kurbo::{Affine, RoundedRectRadii};
use vello::peniko::{Brush, Fill};
use vello::peniko::{Brush, Color, Fill};
use vello::Scene;

use crate::paint_scene_helpers::stroke;
Expand All @@ -25,6 +26,23 @@ struct BorderStyle {
brush: Brush,
}

/// Defines the style of a shadow
struct ShadowStyle {
/// Shadow color
color: Color,
/// Shadow offset from the element
offset: Vec2,
/// Shadow blur radius
blur_radius: f64,
/// Shadow spread radius
spread_radius: f64,
/// The corner radius of the shadow.
///
/// If `None`, the shadow will use the same corner radius as the widget's background.
/// If `Some(radius)`, the shadow will use the specified radius for its corners.
corner_radius: Option<f64>,
}

/// Padding specifies the spacing between the edges of the box and the child view.
///
/// A Padding can also be constructed using [`from(value: f64)`][Self::from]
Expand Down Expand Up @@ -62,6 +80,7 @@ pub struct SizedBox {
height: Option<f64>,
background: Option<Brush>,
border: Option<BorderStyle>,
shadow: Option<ShadowStyle>,
corner_radius: RoundedRectRadii,
padding: Padding,
}
Expand Down Expand Up @@ -191,6 +210,7 @@ impl SizedBox {
height: None,
background: None,
border: None,
shadow: None,
corner_radius: RoundedRectRadii::from_single_radius(0.0),
padding: Padding::ZERO,
}
Expand All @@ -204,6 +224,7 @@ impl SizedBox {
height: None,
background: None,
border: None,
shadow: None,
corner_radius: RoundedRectRadii::from_single_radius(0.0),
padding: Padding::ZERO,
}
Expand All @@ -217,6 +238,7 @@ impl SizedBox {
height: None,
background: None,
border: None,
shadow: None,
corner_radius: RoundedRectRadii::from_single_radius(0.0),
padding: Padding::ZERO,
}
Expand All @@ -234,6 +256,7 @@ impl SizedBox {
height: None,
background: None,
border: None,
shadow: None,
corner_radius: RoundedRectRadii::from_single_radius(0.0),
padding: Padding::ZERO,
}
Expand Down Expand Up @@ -301,6 +324,25 @@ impl SizedBox {
self
}

/// Builder-style method for adding a shadow to the widget.
pub fn shadow(
mut self,
color: impl Into<Color>,
offset: impl Into<Vec2>,
blur_radius: impl Into<f64>,
spread_radius: impl Into<f64>,
corner_radius: impl Into<Option<f64>>,
) -> Self {
self.shadow = Some(ShadowStyle {
color: color.into(),
offset: offset.into(),
blur_radius: blur_radius.into(),
spread_radius: spread_radius.into(),
corner_radius: corner_radius.into(),
});
self
}

/// Builder style method for rounding off corners of this container by setting a corner radius
pub fn rounded(mut self, radius: impl Into<RoundedRectRadii>) -> Self {
self.corner_radius = radius.into();
Expand Down Expand Up @@ -405,6 +447,31 @@ impl SizedBox {
this.ctx.request_layout();
}

/// Add a shadow to the widget.
pub fn set_shadow(
this: &mut WidgetMut<'_, Self>,
color: impl Into<Color>,
offset: impl Into<Vec2>,
blur_radius: impl Into<f64>,
spread_radius: impl Into<f64>,
corner_radius: impl Into<Option<f64>>,
) {
this.widget.shadow = Some(ShadowStyle {
color: color.into(),
offset: offset.into(),
blur_radius: blur_radius.into(),
spread_radius: spread_radius.into(),
corner_radius: corner_radius.into(),
});
this.ctx.request_paint_only();
}

/// Clears shadow.
pub fn clear_shadow(this: &mut WidgetMut<'_, Self>) {
this.widget.shadow = None;
this.ctx.request_paint_only();
}

/// Round off corners of this container by setting a corner radius
pub fn set_rounded(this: &mut WidgetMut<'_, Self>, radius: impl Into<RoundedRectRadii>) {
this.widget.corner_radius = radius.into();
Expand Down Expand Up @@ -520,6 +587,24 @@ impl Widget for SizedBox {

fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
let corner_radius = self.corner_radius;
let size = ctx.size();

// Paint shadow if present
if let Some(shadow) = &self.shadow {
let shadow_rect = size
.to_rect()
.inset(-shadow.spread_radius)
.to_rounded_rect(corner_radius);

scene.draw_blurred_rounded_rect_in(
&shadow_rect,
Affine::translate(shadow.offset),
shadow_rect.rect(),
shadow.color,
shadow.corner_radius.unwrap_or(corner_radius.top_left),
shadow.blur_radius,
);
Comment on lines +594 to +606
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using the clipped version of the blurred rect? The _in version. The unclipped version is how I would expect it to look.
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Oh... It already is using blurred_rounded_rect... Oops)

}

if let Some(background) = self.background.as_mut() {
let panel = ctx.size().to_rounded_rect(corner_radius);
Expand Down Expand Up @@ -716,5 +801,49 @@ mod tests {
assert_render_snapshot!(harness, "label_box_with_outer_padding");
}

#[test]
fn label_box_with_shadow() {
let widget = SizedBox::new(Label::new("hello"))
.width(40.0)
.height(40.0)
.background(palette::css::WHITE)
.shadow(palette::css::BLACK, (5.0, 5.0), 10.0, 0.0, None);

let mut harness = TestHarness::create(widget);

assert_debug_snapshot!(harness.root_widget());
assert_render_snapshot!(harness, "label_box_with_shadow");
}

#[test]
fn label_box_with_shadow_and_border() {
let widget = SizedBox::new(Label::new("hello"))
.width(40.0)
.height(40.0)
.background(palette::css::WHITE)
.border(palette::css::BLUE, 2.0)
.shadow(palette::css::BLACK, (5.0, 5.0), 10.0, 0.0, None);

let mut harness = TestHarness::create(widget);

assert_debug_snapshot!(harness.root_widget());
assert_render_snapshot!(harness, "label_box_with_shadow_and_border");
}

#[test]
fn label_box_with_shadow_and_rounded_corners() {
let widget = SizedBox::new(Label::new("hello"))
.width(40.0)
.height(40.0)
.background(palette::css::WHITE)
.rounded(10.0)
.shadow(palette::css::BLACK, (5.0, 5.0), 10.0, 0.0, None);

let mut harness = TestHarness::create(widget);

assert_debug_snapshot!(harness.root_widget());
assert_render_snapshot!(harness, "label_box_with_shadow_and_rounded_corners");
}

// TODO - add screenshot tests for different brush types
}
Loading
Loading