mirror of https://github.com/linebender/xilem
Enable BackgroundBrush in SizedBox view (#541)
The `SizedBox` widget in Masonry had the option to set a background brush that was not exposed in its corresponding view in Xilem. The `SizedBox` view now exposes the option. Incidentally, `BackgroundBrush::PainterFn` variant had the wrong type, as such it was not usable. A `scene` parameter is added and the closure now requires to be `Send + Sync`. --------- Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
This commit is contained in:
parent
04a8e8ec74
commit
92fc669493
|
@ -142,6 +142,6 @@ pub use parley::layout::Alignment as TextAlignment;
|
|||
pub use util::{AsAny, Handled};
|
||||
pub use vello::peniko::{Color, Gradient};
|
||||
pub use widget::widget::{AllowRawMut, Widget, WidgetId};
|
||||
pub use widget::{BackgroundBrush, WidgetPod, WidgetState};
|
||||
pub use widget::{WidgetPod, WidgetState};
|
||||
|
||||
pub use text_helpers::ArcStr;
|
||||
|
|
|
@ -52,8 +52,6 @@ pub use widget_state::WidgetState;
|
|||
|
||||
pub(crate) use widget_arena::WidgetArena;
|
||||
|
||||
pub use sized_box::BackgroundBrush;
|
||||
|
||||
use crate::{Affine, Size};
|
||||
|
||||
// These are based on https://api.flutter.dev/flutter/painting/BoxFit-class.html
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d089700d96178cbb0b101cfd9c637bb7e41ee2911fa4d15fd66a2ca0067d9c56
|
||||
size 81747
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c8c3813349a80712477a0fe65e931c046c804bb8698da4ecdebd15bd3e59f473
|
||||
size 5421
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ea70d4d9e961064f6988c225eda221719fee5f3fafdce1b41db38a2e3d72bead
|
||||
size 4862
|
|
@ -7,10 +7,10 @@ use accesskit::Role;
|
|||
use smallvec::{smallvec, SmallVec};
|
||||
use tracing::{trace, trace_span, warn, Span};
|
||||
use vello::kurbo::{Affine, RoundedRectRadii};
|
||||
use vello::peniko::{BlendMode, Color, Fill, Gradient};
|
||||
use vello::peniko::{Brush, Color, Fill};
|
||||
use vello::Scene;
|
||||
|
||||
use crate::paint_scene_helpers::{fill_color, stroke};
|
||||
use crate::paint_scene_helpers::stroke;
|
||||
use crate::widget::{WidgetMut, WidgetPod};
|
||||
use crate::{
|
||||
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
|
||||
|
@ -19,16 +19,6 @@ use crate::{
|
|||
|
||||
// FIXME - Improve all doc in this module ASAP.
|
||||
|
||||
/// Something that can be used as the background for a widget.
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub enum BackgroundBrush {
|
||||
Color(Color),
|
||||
Gradient(Gradient),
|
||||
PainterFn(Box<dyn FnMut(&mut PaintCtx)>),
|
||||
}
|
||||
|
||||
/// Something that can be used as the border for a widget.
|
||||
struct BorderStyle {
|
||||
width: f64,
|
||||
|
@ -51,7 +41,7 @@ pub struct SizedBox {
|
|||
child: Option<WidgetPod<Box<dyn Widget>>>,
|
||||
width: Option<f64>,
|
||||
height: Option<f64>,
|
||||
background: Option<BackgroundBrush>,
|
||||
background: Option<Brush>,
|
||||
border: Option<BorderStyle>,
|
||||
corner_radius: RoundedRectRadii,
|
||||
}
|
||||
|
@ -154,9 +144,11 @@ impl SizedBox {
|
|||
|
||||
/// Builder-style method for setting the background for this widget.
|
||||
///
|
||||
/// This can be passed anything which can be represented by a [`BackgroundBrush`];
|
||||
/// notably, it can be any [`Color`], any gradient, or a fully custom painter `FnMut`.
|
||||
pub fn background(mut self, brush: impl Into<BackgroundBrush>) -> Self {
|
||||
/// This can be passed anything which can be represented by a [`Brush`];
|
||||
/// notably, it can be any [`Color`], any gradient, or an [`Image`].
|
||||
///
|
||||
/// [`Image`]: vello::peniko::Image
|
||||
pub fn background(mut self, brush: impl Into<Brush>) -> Self {
|
||||
self.background = Some(brush.into());
|
||||
self
|
||||
}
|
||||
|
@ -234,9 +226,11 @@ impl WidgetMut<'_, SizedBox> {
|
|||
|
||||
/// Set the background for this widget.
|
||||
///
|
||||
/// This can be passed anything which can be represented by a [`BackgroundBrush`];
|
||||
/// notably, it can be any [`Color`], any gradient, or a fully custom painter `FnMut`.
|
||||
pub fn set_background(&mut self, brush: impl Into<BackgroundBrush>) {
|
||||
/// This can be passed anything which can be represented by a [`Brush`];
|
||||
/// notably, it can be any [`Color`], any gradient, or an [`Image`].
|
||||
///
|
||||
/// [`Image`]: vello::peniko::Image
|
||||
pub fn set_background(&mut self, brush: impl Into<Brush>) {
|
||||
self.widget.background = Some(brush.into());
|
||||
self.ctx.request_paint();
|
||||
}
|
||||
|
@ -370,9 +364,13 @@ impl Widget for SizedBox {
|
|||
let panel = ctx.size().to_rounded_rect(corner_radius);
|
||||
|
||||
trace_span!("paint background").in_scope(|| {
|
||||
scene.push_layer(BlendMode::default(), 1., Affine::IDENTITY, &panel);
|
||||
background.paint(ctx, scene);
|
||||
scene.pop_layer();
|
||||
scene.fill(
|
||||
Fill::NonZero,
|
||||
Affine::IDENTITY,
|
||||
&*background,
|
||||
Some(Affine::IDENTITY),
|
||||
&panel,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -414,50 +412,13 @@ impl Widget for SizedBox {
|
|||
}
|
||||
}
|
||||
|
||||
// --- BackgroundBrush ---
|
||||
|
||||
impl BackgroundBrush {
|
||||
/// Draw this brush into a provided [`PaintCtx`].
|
||||
pub fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
|
||||
let bounds = ctx.size().to_rect();
|
||||
match self {
|
||||
Self::Color(color) => fill_color(scene, &bounds, *color),
|
||||
Self::Gradient(grad) => scene.fill(
|
||||
Fill::NonZero,
|
||||
Affine::IDENTITY,
|
||||
&*grad,
|
||||
Some(Affine::IDENTITY),
|
||||
&bounds,
|
||||
),
|
||||
Self::PainterFn(painter) => painter(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for BackgroundBrush {
|
||||
fn from(src: Color) -> BackgroundBrush {
|
||||
BackgroundBrush::Color(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Gradient> for BackgroundBrush {
|
||||
fn from(src: Gradient) -> BackgroundBrush {
|
||||
BackgroundBrush::Gradient(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Painter: FnMut(&mut PaintCtx) + 'static> From<Painter> for BackgroundBrush {
|
||||
fn from(src: Painter) -> BackgroundBrush {
|
||||
BackgroundBrush::PainterFn(Box::new(src))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
// --- MARK: TESTS ---
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
use vello::peniko::Gradient;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_render_snapshot;
|
||||
|
@ -518,7 +479,43 @@ mod tests {
|
|||
let mut harness = TestHarness::create(widget);
|
||||
|
||||
assert_debug_snapshot!(harness.root_widget());
|
||||
assert_render_snapshot!(harness, "label_box_no_size");
|
||||
assert_render_snapshot!(harness, "label_box_with_size");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn label_box_with_solid_background() {
|
||||
let widget = SizedBox::new(Label::new("hello"))
|
||||
.width(40.0)
|
||||
.height(40.0)
|
||||
.background(Color::PLUM);
|
||||
|
||||
let mut harness = TestHarness::create(widget);
|
||||
|
||||
assert_debug_snapshot!(harness.root_widget());
|
||||
assert_render_snapshot!(harness, "label_box_with_solid_background");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_box_with_gradient_background() {
|
||||
let widget = SizedBox::empty()
|
||||
.width(40.)
|
||||
.height(40.)
|
||||
.rounded(20.)
|
||||
.border(Color::LIGHT_SKY_BLUE, 5.)
|
||||
.background(
|
||||
Gradient::new_sweep((30., 30.), 0., std::f32::consts::TAU).with_stops([
|
||||
(0., Color::WHITE),
|
||||
(0.25, Color::BLACK),
|
||||
(0.5, Color::RED),
|
||||
(0.75, Color::GREEN),
|
||||
(1., Color::WHITE),
|
||||
]),
|
||||
);
|
||||
|
||||
let mut harness = TestHarness::create(widget);
|
||||
|
||||
assert_debug_snapshot!(harness.root_widget());
|
||||
assert_render_snapshot!(harness, "empty_box_with_gradient_background");
|
||||
}
|
||||
|
||||
// TODO - add screenshot tests for different brush types
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: masonry/src/widget/sized_box.rs
|
||||
expression: harness.root_widget()
|
||||
---
|
||||
SizedBox
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: masonry/src/widget/sized_box.rs
|
||||
expression: harness.root_widget()
|
||||
---
|
||||
SizedBox(
|
||||
Label<hello>,
|
||||
)
|
|
@ -4,6 +4,8 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use masonry::widget;
|
||||
use vello::kurbo::RoundedRectRadii;
|
||||
use vello::peniko::{Brush, Color};
|
||||
use xilem_core::ViewMarker;
|
||||
|
||||
use crate::{
|
||||
|
@ -24,6 +26,9 @@ where
|
|||
inner,
|
||||
height: None,
|
||||
width: None,
|
||||
background: None,
|
||||
border: None,
|
||||
corner_radius: RoundedRectRadii::from_single_radius(0.0),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +37,9 @@ pub struct SizedBox<V, State, Action = ()> {
|
|||
inner: V,
|
||||
width: Option<f64>,
|
||||
height: Option<f64>,
|
||||
background: Option<Brush>,
|
||||
border: Option<BorderStyle>,
|
||||
corner_radius: RoundedRectRadii,
|
||||
phantom: PhantomData<fn() -> (State, Action)>,
|
||||
}
|
||||
|
||||
|
@ -77,6 +85,32 @@ impl<V, State, Action> SizedBox<V, State, Action> {
|
|||
self.height = Some(f64::INFINITY);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder-style method for setting the background for this widget.
|
||||
///
|
||||
/// This can be passed anything which can be represented by a [`Brush`];
|
||||
/// notably, it can be any [`Color`], any gradient, or an [`Image`].
|
||||
///
|
||||
/// [`Image`]: vello::peniko::Image
|
||||
pub fn background(mut self, brush: impl Into<Brush>) -> Self {
|
||||
self.background = Some(brush.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder-style method for painting a border around the widget with a color and width.
|
||||
pub fn border(mut self, color: impl Into<Color>, width: impl Into<f64>) -> Self {
|
||||
self.border = Some(BorderStyle {
|
||||
color: color.into(),
|
||||
width: width.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();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, State, Action> ViewMarker for SizedBox<V, State, Action> {}
|
||||
|
@ -91,9 +125,16 @@ where
|
|||
|
||||
fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) {
|
||||
let (child, child_state) = self.inner.build(ctx);
|
||||
let widget = widget::SizedBox::new_pod(child.inner.boxed())
|
||||
let mut widget = widget::SizedBox::new_pod(child.inner.boxed())
|
||||
.raw_width(self.width)
|
||||
.raw_height(self.height);
|
||||
.raw_height(self.height)
|
||||
.rounded(self.corner_radius);
|
||||
if let Some(background) = &self.background {
|
||||
widget = widget.background(background.clone());
|
||||
}
|
||||
if let Some(border) = &self.border {
|
||||
widget = widget.border(border.color, border.width);
|
||||
}
|
||||
(Pod::new(widget), child_state)
|
||||
}
|
||||
|
||||
|
@ -116,6 +157,21 @@ where
|
|||
None => element.unset_height(),
|
||||
}
|
||||
}
|
||||
if self.background != prev.background {
|
||||
match &self.background {
|
||||
Some(background) => element.set_background(background.clone()),
|
||||
None => element.clear_background(),
|
||||
}
|
||||
}
|
||||
if self.border != prev.border {
|
||||
match &self.border {
|
||||
Some(border) => element.set_border(border.color, border.width),
|
||||
None => element.clear_border(),
|
||||
}
|
||||
}
|
||||
if self.corner_radius != prev.corner_radius {
|
||||
element.set_rounded(self.corner_radius);
|
||||
}
|
||||
{
|
||||
let mut child = element
|
||||
.child_mut()
|
||||
|
@ -148,3 +204,10 @@ where
|
|||
self.inner.message(view_state, id_path, message, app_state)
|
||||
}
|
||||
}
|
||||
|
||||
/// Something that can be used as the border for a widget.
|
||||
#[derive(PartialEq)]
|
||||
struct BorderStyle {
|
||||
width: f64,
|
||||
color: Color,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue