Skip to content

Commit

Permalink
feat: multiple borders (#889)
Browse files Browse the repository at this point in the history
* feat: per-side border widths

* refactor: make `BorderWidth::is_all_zero` more sane

* fix: wrong operator in `is_all_zero`

* implement parsing for per-side widths

* feat: impl `Display` for `Border`

* fix lints

* fix another lint

* chore: fmt

* fix tests

* fix: add `RRect::rect` to mocked engine

* chore: improve border test coverage

* fix: border test assertion

* fix: radius offset calculations for different border alignments

* clean up border drawing code

* fmt

* revert needless `get_rounded_rect` name change

* format

* add `PathFillType` to mocked engine

* remove `border_align` and place it in main syntax

* add support for multiple borders per element

* mock `Path::set_fill_type`

* update docs

* update `border` attribute documentation

* fix some poor wording in the border docs

* fmt

* fix jagged antialiasing on borders

* lint

* fmt

* mock `Canvas::draw_drrect` and `RRect::bounds`

* update border example

* fix borders not rendering with no background

* feat: per-side border widths (#836)

* feat: per-side border widths

* refactor: make `BorderWidth::is_all_zero` more sane

* fix: wrong operator in `is_all_zero`

* implement parsing for per-side widths

* feat: impl `Display` for `Border`

* fix lints

* fix another lint

* chore: fmt

* fix tests

* fix: add `RRect::rect` to mocked engine

* chore: improve border test coverage

* fix: border test assertion

* fix: radius offset calculations for different border alignments

* clean up border drawing code

* fmt

* revert needless `get_rounded_rect` name change

* format

* add `PathFillType` to mocked engine

* mock `Path::set_fill_type`

* update docs

* fix jagged antialiasing on borders

* lint

* mock `Canvas::draw_drrect` and `RRect::bounds`

* simplify border visibility check

Co-authored-by: Marc Espin <[email protected]>

---------

Co-authored-by: Marc Espin <[email protected]>

* fix: checkbox component

* feat: per-side border widths

* refactor: make `BorderWidth::is_all_zero` more sane

* fix: wrong operator in `is_all_zero`

* implement parsing for per-side widths

* feat: impl `Display` for `Border`

* fix lints

* fix another lint

* chore: fmt

* chore: improve border test coverage

* fix: border test assertion

* fix: radius offset calculations for different border alignments

* clean up border drawing code

* fmt

* revert needless `get_rounded_rect` name change

* format

* remove `border_align` and place it in main syntax

* add support for multiple borders per element

* update docs

* update `border` attribute documentation

* fix some poor wording in the border docs

* fmt

* fix jagged antialiasing on borders

* lint

* fmt

* update border example

* fix borders not rendering with no background

* fix: stray `border_align`

---------

Co-authored-by: Marc Espin <[email protected]>
  • Loading branch information
Tropix126 and marc2332 authored Sep 28, 2024
1 parent b671d55 commit 45b515c
Show file tree
Hide file tree
Showing 25 changed files with 197 additions and 235 deletions.
2 changes: 1 addition & 1 deletion crates/components/src/accordion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub fn Accordion(props: AccordionProps) -> Element {
height: "auto",
background: "{background}",
onclick,
border: "1 solid {border_fill}",
border: "1 inner {border_fill}",
{&props.summary}
rect {
overflow: "clip",
Expand Down
4 changes: 2 additions & 2 deletions crates/components/src/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ pub fn Button(
ButtonStatus::Idle => background,
};
let border = if focus.is_selected() {
format!("2 solid {focus_border_fill}")
format!("2 inner {focus_border_fill}")
} else {
format!("1 solid {border_fill}")
format!("1 inner {border_fill}")
};

rsx!(
Expand Down
5 changes: 2 additions & 3 deletions crates/components/src/checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pub fn Checkbox(
("transparent", unselected_fill.as_ref())
};
let border = if focus.is_selected() {
format!("4 solid {}", border_fill)
format!("4 outer {}", border_fill)
} else {
"none".to_string()
};
Expand All @@ -95,7 +95,6 @@ pub fn Checkbox(
rsx!(
rect {
border,
border_align: "outer",
corner_radius: "4",
rect {
a11y_id: focus.attribute(),
Expand All @@ -105,7 +104,7 @@ pub fn Checkbox(
main_align: "center",
cross_align: "center",
corner_radius: "4",
border: "2 solid {outer_fill}",
border: "2 inner {outer_fill}",
background: "{inner_fill}",
onkeydown,
if selected {
Expand Down
4 changes: 2 additions & 2 deletions crates/components/src/dropdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ where
color: "{font_theme.color}",
corner_radius: "8",
padding: "8 16",
border: "1 solid {border_fill}",
border: "1 inner {border_fill}",
shadow: "0 4 5 0 rgb(0, 0, 0, 0.1)",
direction: "horizontal",
main_align: "center",
Expand All @@ -301,7 +301,7 @@ where
onglobalkeydown,
layer: "-1000",
margin: "{margin}",
border: "1 solid {border_fill}",
border: "1 inner {border_fill}",
overflow: "clip",
corner_radius: "8",
background: "{dropdown_background}",
Expand Down
2 changes: 1 addition & 1 deletion crates/components/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ pub fn Input(
direction: "vertical",
color: "{color}",
background: "{background}",
border: "1 solid {border_fill}",
border: "1 inner {border_fill}",
shadow: "{shadow}",
corner_radius: "{corner_radius}",
margin: "{margin}",
Expand Down
3 changes: 1 addition & 2 deletions crates/components/src/progress_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ pub fn ProgressBar(
background: "{background}",
font_size: "13",
direction: "horizontal",
border: "1 solid {background}",
border_align: "outer",
border: "1 outer {background}",
rect {
corner_radius: "999",
width: "{progress}%",
Expand Down
5 changes: 2 additions & 3 deletions crates/components/src/radio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub fn Radio(
unselected_fill
};
let border = if focus.is_selected() {
format!("4 solid {}", border_fill)
format!("4 outer {}", border_fill)
} else {
"none".to_string()
};
Expand All @@ -79,13 +79,12 @@ pub fn Radio(
rsx!(
rect {
border,
border_align: "outer",
corner_radius: "99",
rect {
a11y_id: focus.attribute(),
width: "18",
height: "18",
border: "2 solid {fill}",
border: "2 inner {fill}",
padding: "4",
main_align: "center",
cross_align: "center",
Expand Down
2 changes: 1 addition & 1 deletion crates/components/src/slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ pub fn Slider(
};

let border = if focus.is_selected() {
format!("2 solid {}", theme.border_fill)
format!("2 inner {}", theme.border_fill)
} else {
"none".to_string()
};
Expand Down
4 changes: 2 additions & 2 deletions crates/components/src/switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,9 @@ pub fn Switch(props: SwitchProps) -> Element {

let border = if focus.is_selected() {
if props.enabled {
format!("2 solid {}", theme.enabled_focus_border_fill)
format!("2 inner {}", theme.enabled_focus_border_fill)
} else {
format!("2 solid {}", theme.focus_border_fill)
format!("2 inner {}", theme.focus_border_fill)
}
} else {
"none".to_string()
Expand Down
2 changes: 1 addition & 1 deletion crates/components/src/tooltip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub fn Tooltip(TooltipProps { text, theme }: TooltipProps) -> Element {
rect {
padding: "4 10",
shadow: "0 0 4 1 rgb(0, 0, 0, 0.1)",
border: "1 solid {border_fill}",
border: "1 inner {border_fill}",
corner_radius: "8",
background: "{background}",
label { max_lines: "1", font_size: "14", color: "{color}", "{text}" }
Expand Down
102 changes: 51 additions & 51 deletions crates/core/src/elements/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,10 +293,9 @@ impl ElementUtils for RectElement {
) {
let node_style = &*node_ref.get::<StyleState>().unwrap();

let mut paint = Paint::default();
let mut path = Path::new();
let area = layout_node.visible_area().to_f32();

let mut path = Path::new();
let mut paint = Paint::default();
paint.set_anti_alias(true);
paint.set_style(PaintStyle::Fill);

Expand Down Expand Up @@ -344,8 +343,10 @@ impl ElementUtils for RectElement {
for mut shadow in node_style.shadows.clone().into_iter() {
if shadow.fill != Fill::Color(Color::TRANSPARENT) {
shadow.scale(scale_factor);
let mut shadow_paint = paint.clone();

let mut shadow_path = Path::new();
let mut shadow_paint = Paint::default();
shadow_paint.set_anti_alias(true);

match &shadow.fill {
Fill::Color(color) => {
Expand Down Expand Up @@ -418,35 +419,37 @@ impl ElementUtils for RectElement {
}

// Borders
if node_style.border.is_visible() {
let mut border = node_style.border.clone();
border.scale(scale_factor);

// Create a new paint
let mut border_paint = paint.clone();
border_paint.set_anti_alias(true);
border_paint.set_style(PaintStyle::Fill);
match &node_style.border.fill {
Fill::Color(color) => {
border_paint.set_color(*color);
}
Fill::LinearGradient(gradient) => {
border_paint.set_shader(gradient.into_shader(area));
}
Fill::RadialGradient(gradient) => {
border_paint.set_shader(gradient.into_shader(area));
}
Fill::ConicGradient(gradient) => {
border_paint.set_shader(gradient.into_shader(area));
}
}
for mut border in node_style.borders.clone().into_iter() {
if border.is_visible() {
border.scale(scale_factor);

match Self::border_shape(*rounded_rect.rect(), corner_radius, &border) {
BorderShape::DRRect(outer, inner) => {
canvas.draw_drrect(outer, inner, &border_paint);
// Create a new paint
let mut border_paint = Paint::default();
border_paint.set_style(PaintStyle::Fill);
border_paint.set_anti_alias(true);

match &border.fill {
Fill::Color(color) => {
border_paint.set_color(*color);
}
Fill::LinearGradient(gradient) => {
border_paint.set_shader(gradient.into_shader(area));
}
Fill::RadialGradient(gradient) => {
border_paint.set_shader(gradient.into_shader(area));
}
Fill::ConicGradient(gradient) => {
border_paint.set_shader(gradient.into_shader(area));
}
}
BorderShape::Path(path) => {
canvas.draw_path(&path, &border_paint);

match Self::border_shape(*rounded_rect.rect(), corner_radius, &border) {
BorderShape::DRRect(outer, inner) => {
canvas.draw_drrect(outer, inner, &border_paint);
}
BorderShape::Path(path) => {
canvas.draw_path(&path, &border_paint);
}
}
}
}
Expand All @@ -468,8 +471,7 @@ impl ElementUtils for RectElement {
fn element_needs_cached_area(&self, node_ref: &DioxusNode) -> bool {
let node_style = &*node_ref.get::<StyleState>().unwrap();

node_style.border.is_visible() && node_style.border.alignment != BorderAlignment::Inner
|| !node_style.shadows.is_empty()
!node_style.borders.is_empty() || !node_style.shadows.is_empty()
}

fn element_drawing_area(
Expand All @@ -481,10 +483,7 @@ impl ElementUtils for RectElement {
let node_style = &*node_ref.get::<StyleState>().unwrap();
let mut area = layout_node.visible_area();

if !node_style.border.is_visible()
&& node_style.border.alignment != BorderAlignment::Inner
&& node_style.shadows.is_empty()
{
if node_style.borders.is_empty() && node_style.shadows.is_empty() {
return area;
}

Expand Down Expand Up @@ -557,22 +556,23 @@ impl ElementUtils for RectElement {
}
}

if node_style.border.is_visible() {
let mut border = node_style.border.clone();
border.scale(scale_factor);
for mut border in node_style.borders.clone().into_iter() {
if border.is_visible() {
border.scale(scale_factor);

let border_shape =
Self::border_shape(*rounded_rect.rect(), node_style.corner_radius, &border);
let border_bounds = match border_shape {
BorderShape::DRRect(ref outer, _) => outer.bounds(),
BorderShape::Path(ref path) => path.bounds(),
};
let border_area = Area::new(
Point2D::new(border_bounds.x(), border_bounds.y()),
Size2D::new(border_bounds.width(), border_bounds.height()),
);
let border_shape =
Self::border_shape(*rounded_rect.rect(), node_style.corner_radius, &border);
let border_bounds = match border_shape {
BorderShape::DRRect(ref outer, _) => outer.bounds(),
BorderShape::Path(ref path) => path.bounds(),
};
let border_area = Area::new(
Point2D::new(border_bounds.x(), border_bounds.y()),
Size2D::new(border_bounds.width(), border_bounds.height()),
);

area = area.union(&border_area.round_out());
area = area.union(&border_area.round_out());
}
}

area
Expand Down
6 changes: 5 additions & 1 deletion crates/core/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ impl NodeState {
};
("background", fill)
},
("border", AttributeType::Border(&self.style.border)),
(
"corner_radius",
AttributeType::CornerRadius(self.style.corner_radius),
Expand Down Expand Up @@ -145,6 +144,11 @@ impl NodeState {
attributes.push(("shadow", AttributeType::Shadow(shadow)));
}

let borders = &self.style.borders;
for border in borders {
attributes.push(("border", AttributeType::Border(border)));
}

let text_shadows = &self.font_style.text_shadows;

for text_shadow in text_shadows {
Expand Down
2 changes: 1 addition & 1 deletion crates/devtools/src/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ pub fn BorderProperty(name: String, border: Border) -> Element {
text {
font_size: "15",
color: "rgb(252,181,172)",
"{border.width} {border.style:?} {border.alignment:?}"
"{border.width} {border.alignment:?}"
}
}
rect {
Expand Down
57 changes: 41 additions & 16 deletions crates/elements/src/_docs/attributes/border.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,65 @@
### border & border_align
### border

You can add a border to an element using the `border` and `border_align` attributes.
- `border` syntax: `[width] [width?] [width?] [width?] <solid | none> [color]`.
- `border_align` syntax: `<inner | outer | center>`.
You can add borders to an element using the `border` attribute.
- `border` syntax: `[width] [width?] [width?] [width?] <inner | outer | center> [fill]`.

1-4 width values can be provided with the `border` attribute. Widths will be applied to different sides of a `rect` depending on the number of values:
- One value: `[all]`
- Two values: `[vertical]` `[horizontal]`
- Three values: `[top]` `[horizontal]` `[bottom]`
- Four values: `[top]` `[right]` `[bottom]` `[left]`
1-4 width values should be provided with the `border` attribute. Widths will be applied to different sides of a `rect` depending on the number of values provided:
- One value: `all`
- Two values: `vertical`, `horizontal`
- Three values: `top` `horizontal` `bottom`
- Four values: `top` `right` `bottom` `left`

### Example
*Border alignment* determines how the border is positioned relative to the element's edge. Alignment can be `inner`, `outer`, or `center`.

A solid, black border with a width of 2 pixels on every side. Border is aligned to the inside of the rect's bounding box.
### Examples

A solid, black border with a width of 2 pixels on every side. Border is aligned to the inside of the rect's edge.

```rust, no_run
# use freya::prelude::*;
fn app() -> Element {
rsx!(
rect {
border: "2 inner black",
}
)
}
```

A solid, red border with different widths on each side. Border is aligned to the center of the rect's edge.

```rust, no_run
# use freya::prelude::*;
fn app() -> Element {
rsx!(
rect {
border: "1 2 3 4 center red",
}
)
}
```

Borders can take any valid fill type, including gradients.

```rust, no_run
# use freya::prelude::*;
fn app() -> Element {
rsx!(
rect {
border: "2 solid black",
border_align: "inner"
border: "1 inner linear-gradient(red, green, yellow 40%, blue)",
}
)
}
```

Same as above, but with different border widths on each side.
Similarly to the `shadow` attribute, multiple borders can be drawn on a single element when separated by a comma. Borders specified later in the list are drawn on top of previous ones.

```rust, no_run
# use freya::prelude::*;
fn app() -> Element {
rsx!(
rect {
border: "1 2 3 4 solid black",
border_align: "inner"
border: "6 outer red, 5 outer orange, 4 outer yellow, 3 outer green, 2 outer blue, 1 outer purple",
}
)
}
Expand Down
Loading

0 comments on commit 45b515c

Please sign in to comment.