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

Add padding attribute to sized_box view #736

Merged
merged 7 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 77 additions & 3 deletions masonry/src/widget/sized_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use accesskit::{NodeBuilder, Role};
use smallvec::{smallvec, SmallVec};
use tracing::{trace_span, warn, Span};
use vello::kurbo::{Affine, RoundedRectRadii};
use vello::kurbo::{Affine, Insets, RoundedRectRadii};
use vello::peniko::{Brush, Color, Fill};
use vello::Scene;

Expand All @@ -26,7 +26,6 @@ struct BorderStyle {
}

// TODO - Have Widget type as generic argument
// TODO - Add Padding

/// A widget with predefined size.
///
Expand All @@ -44,6 +43,7 @@ pub struct SizedBox {
background: Option<Brush>,
border: Option<BorderStyle>,
corner_radius: RoundedRectRadii,
padding: Insets,
}

// --- MARK: BUILDERS ---
Expand All @@ -57,6 +57,7 @@ impl SizedBox {
background: None,
border: None,
corner_radius: RoundedRectRadii::from_single_radius(0.0),
padding: Insets::ZERO,
}
}

Expand All @@ -69,6 +70,7 @@ impl SizedBox {
background: None,
border: None,
corner_radius: RoundedRectRadii::from_single_radius(0.0),
padding: Insets::ZERO,
}
}

Expand All @@ -81,6 +83,7 @@ impl SizedBox {
background: None,
border: None,
corner_radius: RoundedRectRadii::from_single_radius(0.0),
padding: Insets::ZERO,
}
}

Expand All @@ -97,6 +100,7 @@ impl SizedBox {
background: None,
border: None,
corner_radius: RoundedRectRadii::from_single_radius(0.0),
padding: Insets::ZERO,
}
}

Expand Down Expand Up @@ -180,6 +184,11 @@ impl SizedBox {
self
}

pub fn padding(mut self, padding: impl Into<Insets>) -> Self {
viktorstrate marked this conversation as resolved.
Show resolved Hide resolved
self.padding = padding.into();
self
}

// TODO - child()
}

Expand Down Expand Up @@ -266,6 +275,18 @@ impl SizedBox {
this.ctx.request_paint_only();
}

/// Clears padding.
pub fn clear_padding(this: &mut WidgetMut<'_, Self>) {
this.widget.padding = Insets::ZERO;
this.ctx.request_paint_only();
viktorstrate marked this conversation as resolved.
Show resolved Hide resolved
}

/// Set the padding around this widget.
pub fn set_padding(this: &mut WidgetMut<'_, Self>, padding: impl Into<Insets>) {
this.widget.padding = padding.into();
this.ctx.request_paint_only();
viktorstrate marked this conversation as resolved.
Show resolved Hide resolved
}

// TODO - Doc
pub fn child_mut<'t>(
this: &'t mut WidgetMut<'_, Self>,
Expand Down Expand Up @@ -333,6 +354,14 @@ impl Widget for SizedBox {
let child_bc = child_bc.shrink((2.0 * border_width, 2.0 * border_width));
let origin = Point::new(border_width, border_width);

// Shrink constraints by padding inset
let padding_size = Size::new(
self.padding.x0 + self.padding.x1,
self.padding.y0 + self.padding.y1,
);
let child_bc = child_bc.shrink(padding_size);
let origin = origin + (self.padding.x0, self.padding.y0);

let mut size;
match self.child.as_mut() {
Some(child) => {
Expand All @@ -341,7 +370,7 @@ impl Widget for SizedBox {
size = Size::new(
size.width + 2.0 * border_width,
size.height + 2.0 * border_width,
);
) + padding_size;
}
None => size = bc.constrain((self.width.unwrap_or(0.0), self.height.unwrap_or(0.0))),
};
Expand Down Expand Up @@ -476,6 +505,19 @@ mod tests {
assert_render_snapshot!(harness, "label_box_with_size");
}

#[test]
fn label_box_with_padding() {
let widget = SizedBox::new(Label::new("hello"))
.border(Color::BLUE, 5.0)
.rounded(5.0)
.padding((60., 40.));

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

assert_debug_snapshot!(harness.root_widget());
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure these debug snapshot tests are needed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I remove them? I just copied them from the other test cases in the file.

Copy link
Member

Choose a reason for hiding this comment

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

We can leave them from consistency. @PoignardAzur what are these snapshots actually testing?

assert_render_snapshot!(harness, "label_box_with_padding");
}

#[test]
fn label_box_with_solid_background() {
let widget = SizedBox::new(Label::new("hello"))
Expand Down Expand Up @@ -512,5 +554,37 @@ mod tests {
assert_render_snapshot!(harness, "empty_box_with_gradient_background");
}

#[test]
fn label_box_with_padding_and_background() {
let widget = SizedBox::new(Label::new("hello"))
.width(40.0)
.height(40.0)
.background(Color::PLUM)
.border(Color::LIGHT_SKY_BLUE, 5.)
.padding(100.);

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

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

#[test]
fn label_box_with_padding_outside() {
let widget = SizedBox::new(
SizedBox::new(Label::new("hello"))
.width(40.0)
.height(40.0)
.background(Color::PLUM)
.border(Color::LIGHT_SKY_BLUE, 5.),
)
.padding(100.);

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

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

// TODO - add screenshot tests for different brush types
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
source: masonry/src/widget/sized_box.rs
expression: harness.root_widget()
---
SizedBox(
Label<hello>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
source: masonry/src/widget/sized_box.rs
expression: harness.root_widget()
---
SizedBox(
Label<hello>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: masonry/src/widget/sized_box.rs
expression: harness.root_widget()
---
SizedBox(
SizedBox(
Label<hello>,
),
)
12 changes: 7 additions & 5 deletions xilem/examples/http_cats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use xilem::core::fork;
use xilem::core::one_of::OneOf3;
use xilem::view::{
button, flex, image, inline_prose, portal, prose, sized_box, spinner, worker, Axis, FlexExt,
FlexSpacer,
FlexSpacer, Padding,
};
use xilem::{Color, EventLoop, EventLoopBuilder, TextAlignment, WidgetView, Xilem};

Expand Down Expand Up @@ -47,13 +47,15 @@ enum ImageState {

impl HttpCats {
fn view(&mut self) -> impl WidgetView<HttpCats> {
let left_column = portal(flex((
let left_column = sized_box(portal(flex((
prose("Status"),
self.statuses
.iter_mut()
.map(Status::list_view)
.collect::<Vec<_>>(),
)));
))))
.padding(Padding::leading(20.));
viktorstrate marked this conversation as resolved.
Show resolved Hide resolved

let (info_area, worker_value) = if let Some(selected_code) = self.selected_code {
if let Some(selected_status) =
self.statuses.iter_mut().find(|it| it.code == selected_code)
Expand Down Expand Up @@ -197,8 +199,8 @@ impl Status {
FlexSpacer::Fixed(10.),
image,
// TODO: Overlay on top of the image?
// HACK: Trailing spaces workaround scrollbar covering content
prose("Copyright ©️ https://http.cat ").alignment(TextAlignment::End),
sized_box(prose("Copyright ©️ https://http.cat").alignment(TextAlignment::End))
.padding(Padding::trailing(20.)),
viktorstrate marked this conversation as resolved.
Show resolved Hide resolved
viktorstrate marked this conversation as resolved.
Show resolved Hide resolved
))
.main_axis_alignment(xilem::view::MainAxisAlignment::Start)
}
Expand Down
99 changes: 96 additions & 3 deletions xilem/src/view/sized_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use std::marker::PhantomData;

use masonry::widget;
use masonry::{widget, Insets};
use vello::kurbo::RoundedRectRadii;
use vello::peniko::{Brush, Color};

Expand All @@ -26,6 +26,7 @@ where
background: None,
border: None,
corner_radius: RoundedRectRadii::from_single_radius(0.0),
padding: Padding::ZERO,
phantom: PhantomData,
}
}
Expand All @@ -37,6 +38,7 @@ pub struct SizedBox<V, State, Action = ()> {
background: Option<Brush>,
border: Option<BorderStyle>,
corner_radius: RoundedRectRadii,
padding: Padding,
phantom: PhantomData<fn() -> (State, Action)>,
}

Expand Down Expand Up @@ -103,11 +105,17 @@ impl<V, State, Action> SizedBox<V, State, Action> {
self
}

/// Builder style method for rounding off corners of this container by setting a corner radius
/// Builder style method for rounding off corners of this container by setting a corner radius.
viktorstrate marked this conversation as resolved.
Show resolved Hide resolved
pub fn rounded(mut self, radius: impl Into<RoundedRectRadii>) -> Self {
self.corner_radius = radius.into();
self
}

/// Builder style method for adding a padding around the widget.
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
self.padding = padding.into();
self
}
}

impl<V, State, Action> ViewMarker for SizedBox<V, State, Action> {}
Expand All @@ -125,7 +133,8 @@ where
let mut widget = widget::SizedBox::new_pod(child.inner.boxed())
.raw_width(self.width)
.raw_height(self.height)
.rounded(self.corner_radius);
.rounded(self.corner_radius)
.padding(self.padding);
if let Some(background) = &self.background {
widget = widget.background(background.clone());
}
Expand Down Expand Up @@ -173,6 +182,9 @@ where
if self.corner_radius != prev.corner_radius {
widget::SizedBox::set_rounded(&mut element, self.corner_radius);
}
if self.padding != prev.padding {
widget::SizedBox::set_padding(&mut element, self.padding);
}
{
let mut child = widget::SizedBox::child_mut(&mut element)
.expect("We only create SizedBox with a child");
Expand Down Expand Up @@ -209,3 +221,84 @@ struct BorderStyle {
width: f64,
color: Color,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Padding {
DJMcNab marked this conversation as resolved.
Show resolved Hide resolved
viktorstrate marked this conversation as resolved.
Show resolved Hide resolved
top: f64,
viktorstrate marked this conversation as resolved.
Show resolved Hide resolved
trailing: f64,
bottom: f64,
leading: f64,
}

impl Padding {
pub const fn new(top: f64, trailing: f64, bottom: f64, leading: f64) -> Self {
Self {
top,
trailing,
bottom,
leading,
}
}

pub const ZERO: Padding = Padding::all(0.);

pub const fn all(padding: f64) -> Self {
Self::new(padding, padding, padding, padding)
}

pub const fn horizontal(padding: f64) -> Self {
Self::new(0., padding, 0., padding)
}

pub const fn vertical(padding: f64) -> Self {
Self::new(padding, 0., padding, 0.)
}

pub const fn top(padding: f64) -> Self {
Self::new(padding, 0., 0., 0.)
}

pub const fn trailing(padding: f64) -> Self {
Self::new(0., padding, 0., 0.)
}

pub const fn bottom(padding: f64) -> Self {
Self::new(0., 0., padding, 0.)
}

pub const fn leading(padding: f64) -> Self {
DJMcNab marked this conversation as resolved.
Show resolved Hide resolved
Self::new(0., 0., 0., padding)
}
}

impl From<f64> for Padding {
fn from(value: f64) -> Self {
Self::all(value)
}
}

// Follows CSS padding order for 4 values (top, trailing, bottom, leading)
impl From<(f64, f64, f64, f64)> for Padding {
viktorstrate marked this conversation as resolved.
Show resolved Hide resolved
fn from(value: (f64, f64, f64, f64)) -> Self {
Self::new(value.0, value.1, value.2, value.3)
}
}

// Follows CSS padding order for 2 values (vertical, horizontal)
impl From<(f64, f64)> for Padding {
fn from(value: (f64, f64)) -> Self {
Self::new(value.0, value.1, value.0, value.1)
}
}

impl From<Insets> for Padding {
fn from(value: Insets) -> Self {
Self::new(value.y0, value.x1, value.y1, value.x0)
}
}

impl From<Padding> for Insets {
fn from(value: Padding) -> Self {
Insets::new(value.leading, value.top, value.trailing, value.bottom)
}
}