From c792105bc0323959242e4dac936cf3bf21f89e5b Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Sat, 31 Aug 2024 21:06:29 -0500 Subject: [PATCH 1/7] feat: round to physical pixel boundaries while rendering --- crates/core/src/elements/image.rs | 2 +- crates/core/src/elements/label.rs | 2 +- crates/core/src/elements/paragraph.rs | 2 +- crates/core/src/elements/rect.rs | 4 ++-- crates/core/src/elements/svg.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/core/src/elements/image.rs b/crates/core/src/elements/image.rs index bbc49c111..17251e881 100644 --- a/crates/core/src/elements/image.rs +++ b/crates/core/src/elements/image.rs @@ -21,7 +21,7 @@ impl ElementUtils for ImageElement { _default_fonts: &[String], _scale_factor: f32, ) { - let area = layout_node.visible_area(); + let area = layout_node.visible_area().round(); let node_style = node_ref.get::().unwrap(); let node_references = node_ref.get::().unwrap(); diff --git a/crates/core/src/elements/label.rs b/crates/core/src/elements/label.rs index 0af638c15..2731d6590 100644 --- a/crates/core/src/elements/label.rs +++ b/crates/core/src/elements/label.rs @@ -27,7 +27,7 @@ impl ElementUtils for LabelElement { .get::() .unwrap() .0; - let area = layout_node.visible_area(); + let area = layout_node.visible_area().round(); let x = area.min_x(); let y = area.min_y() + align_main_align_paragraph(node_ref, &area, paragraph); diff --git a/crates/core/src/elements/paragraph.rs b/crates/core/src/elements/paragraph.rs index 512ae7c41..74d5cb4ed 100644 --- a/crates/core/src/elements/paragraph.rs +++ b/crates/core/src/elements/paragraph.rs @@ -27,7 +27,7 @@ impl ElementUtils for ParagraphElement { default_fonts: &[String], scale_factor: f32, ) { - let area = layout_node.visible_area(); + let area = layout_node.visible_area().round(); let node_cursor_state = &*node_ref.get::().unwrap(); let paint = |paragraph: &Paragraph| { diff --git a/crates/core/src/elements/rect.rs b/crates/core/src/elements/rect.rs index bcd831d40..9ea5679cf 100644 --- a/crates/core/src/elements/rect.rs +++ b/crates/core/src/elements/rect.rs @@ -29,7 +29,7 @@ impl RectElement { node_ref: &DioxusNode, scale_factor: f32, ) -> RRect { - let area = layout_node.visible_area().to_f32(); + let area = layout_node.visible_area().to_f32().round(); let node_style = &*node_ref.get::().unwrap(); let mut radius = node_style.corner_radius; radius.scale(scale_factor); @@ -85,7 +85,7 @@ impl ElementUtils for RectElement { let mut paint = Paint::default(); let mut path = Path::new(); - let area = layout_node.visible_area().to_f32(); + let area = layout_node.visible_area().to_f32().round(); paint.set_anti_alias(true); paint.set_style(PaintStyle::Fill); diff --git a/crates/core/src/elements/svg.rs b/crates/core/src/elements/svg.rs index e0685d816..ab148da85 100644 --- a/crates/core/src/elements/svg.rs +++ b/crates/core/src/elements/svg.rs @@ -19,7 +19,7 @@ impl ElementUtils for SvgElement { _default_fonts: &[String], _scale_factor: f32, ) { - let area = layout_node.visible_area(); + let area = layout_node.visible_area().round(); let node_style = &*node_ref.get::().unwrap(); let x = area.min_x(); From b232247f9d2375f7b59d91787c0f193c4a50a90b Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Sun, 1 Sep 2024 09:42:11 -0500 Subject: [PATCH 2/7] feat: add attribute to disable snapping --- crates/core/src/node.rs | 2 ++ crates/devtools/src/tabs/style.rs | 13 +++++++++++++ crates/native-core/src/attributes.rs | 2 ++ crates/state/src/style.rs | 10 ++++++++++ 4 files changed, 27 insertions(+) diff --git a/crates/core/src/node.rs b/crates/core/src/node.rs index 11509017d..13aa4782e 100644 --- a/crates/core/src/node.rs +++ b/crates/core/src/node.rs @@ -138,6 +138,7 @@ impl NodeState { ("offset_x", AttributeType::Measure(self.size.offset_x.get())), ("offset_y", AttributeType::Measure(self.size.offset_y.get())), ("content", AttributeType::Content(&self.size.content)), + ("subpixel_rounding", AttributeType::SubpixelRounding(self.style.subpixel_rounding)) ]; let shadows = &self.style.shadows; @@ -169,6 +170,7 @@ pub enum AttributeType<'a> { Shadow(&'a Shadow), TextShadow(&'a TextShadow), Text(String), + SubpixelRounding(bool), Border(&'a Border), TextAlignment(&'a TextAlign), TextOverflow(&'a TextOverflow), diff --git a/crates/devtools/src/tabs/style.rs b/crates/devtools/src/tabs/style.rs index c90f288f4..53d52e0e0 100644 --- a/crates/devtools/src/tabs/style.rs +++ b/crates/devtools/src/tabs/style.rs @@ -181,6 +181,19 @@ pub fn NodeInspectorStyle(node_id: String) -> Element { } } } + AttributeType::SubpixelRounding(enabled) => { + rsx!{ + Property { + key: "{i}", + name: "{name}", + value: if enabled { + "round" + } else { + "none" + } + } + } + } } })} } diff --git a/crates/native-core/src/attributes.rs b/crates/native-core/src/attributes.rs index d853942f4..42e3cb060 100644 --- a/crates/native-core/src/attributes.rs +++ b/crates/native-core/src/attributes.rs @@ -44,6 +44,7 @@ pub enum AttributeName { PositionLeft, Opacity, Content, + SubpixelRounding, Name, Focusable, Role, @@ -115,6 +116,7 @@ impl FromStr for AttributeName { "position_left" => Ok(AttributeName::PositionLeft), "opacity" => Ok(AttributeName::Opacity), "content" => Ok(AttributeName::Content), + "subpixel_rounding" => Ok(AttributeName::SubpixelRounding), "name" => Ok(AttributeName::Name), "focusable" => Ok(AttributeName::Focusable), "role" => Ok(AttributeName::Role), diff --git a/crates/state/src/style.rs b/crates/state/src/style.rs index 8b142059c..4dac6c342 100644 --- a/crates/state/src/style.rs +++ b/crates/state/src/style.rs @@ -38,6 +38,7 @@ pub struct StyleState { pub svg_data: Option, pub overflow: OverflowMode, pub opacity: Option, + pub subpixel_rounding: bool, } impl ParseAttribute for StyleState { @@ -119,6 +120,15 @@ impl ParseAttribute for StyleState { self.opacity = Some(value.parse::().map_err(|_| ParseError)?); } } + AttributeName::SubpixelRounding => { + if let Some(value) = attr.value.as_text() { + match value { + "round" => self.subpixel_rounding = true, + "none" => self.subpixel_rounding = false, + _ => {} + } + } + } _ => {} } From 935935e639b600d831bc93aef4d075bb344bded3 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Sun, 1 Sep 2024 10:05:19 -0500 Subject: [PATCH 3/7] feat: implement and document `subpixel_rounding` attribute --- crates/core/src/elements/image.rs | 5 ++++- crates/core/src/elements/label.rs | 2 +- crates/core/src/elements/paragraph.rs | 2 +- crates/core/src/elements/rect.rs | 6 +++++- crates/core/src/elements/svg.rs | 7 ++++++- .../src/_docs/attributes/subpixel_rounding.md | 18 ++++++++++++++++ crates/elements/src/definitions.rs | 6 ++++++ crates/state/src/style.rs | 21 +++++++++++++++++-- examples/border.rs | 1 + 9 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 crates/elements/src/_docs/attributes/subpixel_rounding.md diff --git a/crates/core/src/elements/image.rs b/crates/core/src/elements/image.rs index 17251e881..d49f451e0 100644 --- a/crates/core/src/elements/image.rs +++ b/crates/core/src/elements/image.rs @@ -21,9 +21,12 @@ impl ElementUtils for ImageElement { _default_fonts: &[String], _scale_factor: f32, ) { - let area = layout_node.visible_area().round(); let node_style = node_ref.get::().unwrap(); let node_references = node_ref.get::().unwrap(); + let mut area = layout_node.visible_area().round(); + if node_style.subpixel_rounding { + area = area.round(); + } let draw_img = |bytes: &[u8]| { let pic = Image::from_encoded(unsafe { Data::new_bytes(bytes) }); diff --git a/crates/core/src/elements/label.rs b/crates/core/src/elements/label.rs index 2731d6590..0af638c15 100644 --- a/crates/core/src/elements/label.rs +++ b/crates/core/src/elements/label.rs @@ -27,7 +27,7 @@ impl ElementUtils for LabelElement { .get::() .unwrap() .0; - let area = layout_node.visible_area().round(); + let area = layout_node.visible_area(); let x = area.min_x(); let y = area.min_y() + align_main_align_paragraph(node_ref, &area, paragraph); diff --git a/crates/core/src/elements/paragraph.rs b/crates/core/src/elements/paragraph.rs index 74d5cb4ed..512ae7c41 100644 --- a/crates/core/src/elements/paragraph.rs +++ b/crates/core/src/elements/paragraph.rs @@ -27,7 +27,7 @@ impl ElementUtils for ParagraphElement { default_fonts: &[String], scale_factor: f32, ) { - let area = layout_node.visible_area().round(); + let area = layout_node.visible_area(); let node_cursor_state = &*node_ref.get::().unwrap(); let paint = |paragraph: &Paragraph| { diff --git a/crates/core/src/elements/rect.rs b/crates/core/src/elements/rect.rs index 9ea5679cf..425693f0b 100644 --- a/crates/core/src/elements/rect.rs +++ b/crates/core/src/elements/rect.rs @@ -85,7 +85,11 @@ impl ElementUtils for RectElement { let mut paint = Paint::default(); let mut path = Path::new(); - let area = layout_node.visible_area().to_f32().round(); + let mut area = layout_node.visible_area().to_f32(); + + if node_style.subpixel_rounding { + area = area.round(); + } paint.set_anti_alias(true); paint.set_style(PaintStyle::Fill); diff --git a/crates/core/src/elements/svg.rs b/crates/core/src/elements/svg.rs index ab148da85..9681e7d1d 100644 --- a/crates/core/src/elements/svg.rs +++ b/crates/core/src/elements/svg.rs @@ -19,9 +19,14 @@ impl ElementUtils for SvgElement { _default_fonts: &[String], _scale_factor: f32, ) { - let area = layout_node.visible_area().round(); let node_style = &*node_ref.get::().unwrap(); + let mut area = layout_node.visible_area().round(); + + if node_style.subpixel_rounding { + area = area.round(); + } + let x = area.min_x(); let y = area.min_y(); if let Some(svg_data) = &node_style.svg_data { diff --git a/crates/elements/src/_docs/attributes/subpixel_rounding.md b/crates/elements/src/_docs/attributes/subpixel_rounding.md new file mode 100644 index 000000000..95a11ef31 --- /dev/null +++ b/crates/elements/src/_docs/attributes/subpixel_rounding.md @@ -0,0 +1,18 @@ +Determines whether or not elements rendered on a subpixel boundary should be rounded to a physical pixel. Rounding provides better visual clarity (sharp edges/borders on rectangles), but may result in some layouts appearing visually offcenter at small sizes. + +If unspecified, rendering will be rounded to physical pixels. + +Syntax: `<"round" | "none">` + +### Example + +```rust, no_run +# use freya::prelude::*; +fn app() -> Element { + rsx!( + rect { + subpixel_rounding: "none" + } + ) +} +``` \ No newline at end of file diff --git a/crates/elements/src/definitions.rs b/crates/elements/src/definitions.rs index fe1b21176..826f27e0c 100644 --- a/crates/elements/src/definitions.rs +++ b/crates/elements/src/definitions.rs @@ -230,6 +230,8 @@ builder_constructors! { content: String, #[doc = include_str!("_docs/attributes/line_height.md")] line_height: String, + #[doc = include_str!("_docs/attributes/subpixel_rounding.md")] + subpixel_rounding: String, name: String, focusable: String, @@ -449,6 +451,8 @@ builder_constructors! { rotate: String, #[doc = include_str!("_docs/attributes/opacity.md")] opacity: String, + #[doc = include_str!("_docs/attributes/subpixel_rounding.md")] + subpixel_rounding: String, image_data: String, image_reference: String, @@ -488,6 +492,8 @@ builder_constructors! { rotate: String, #[doc = include_str!("_docs/attributes/opacity.md")] opacity: String, + #[doc = include_str!("_docs/attributes/subpixel_rounding.md")] + subpixel_rounding: String, svg_data: String, svg_content: String, diff --git a/crates/state/src/style.rs b/crates/state/src/style.rs index 4dac6c342..b4963a7c9 100644 --- a/crates/state/src/style.rs +++ b/crates/state/src/style.rs @@ -28,7 +28,7 @@ use crate::{ Shadow, }; -#[derive(Default, Debug, Clone, PartialEq, Component)] +#[derive(Debug, Clone, PartialEq, Component)] pub struct StyleState { pub background: Fill, pub border: Border, @@ -41,6 +41,22 @@ pub struct StyleState { pub subpixel_rounding: bool, } +impl Default for StyleState { + fn default() -> Self { + Self { + background: Default::default(), + border: Default::default(), + shadows: Default::default(), + corner_radius: Default::default(), + image_data: Default::default(), + svg_data: Default::default(), + overflow: Default::default(), + opacity: Default::default(), + subpixel_rounding: true, + } + } +} + impl ParseAttribute for StyleState { fn parse_attribute( &mut self, @@ -125,7 +141,7 @@ impl ParseAttribute for StyleState { match value { "round" => self.subpixel_rounding = true, "none" => self.subpixel_rounding = false, - _ => {} + _ => return Err(ParseError) } } } @@ -158,6 +174,7 @@ impl State for StyleState { AttributeName::SvgContent, AttributeName::Overflow, AttributeName::Opacity, + AttributeName::SubpixelRounding, ])); fn update<'a>( diff --git a/examples/border.rs b/examples/border.rs index 87ef8586e..8617aaf88 100644 --- a/examples/border.rs +++ b/examples/border.rs @@ -27,6 +27,7 @@ fn app() -> Element { background: "rgb(0, 0, 0)", border: "1 solid rgb(242, 151, 39)", border_align: "inner", + subpixel_rounding: "none", } rect { width: "80", From dbb078a6ba7193267154b0ea77fc0d3ab1051703 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Sun, 1 Sep 2024 10:08:23 -0500 Subject: [PATCH 4/7] chore: fmt --- crates/core/src/elements/svg.rs | 2 +- crates/core/src/node.rs | 5 ++++- crates/state/src/style.rs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/core/src/elements/svg.rs b/crates/core/src/elements/svg.rs index 9681e7d1d..570550531 100644 --- a/crates/core/src/elements/svg.rs +++ b/crates/core/src/elements/svg.rs @@ -22,7 +22,7 @@ impl ElementUtils for SvgElement { let node_style = &*node_ref.get::().unwrap(); let mut area = layout_node.visible_area().round(); - + if node_style.subpixel_rounding { area = area.round(); } diff --git a/crates/core/src/node.rs b/crates/core/src/node.rs index 13aa4782e..c80f837e8 100644 --- a/crates/core/src/node.rs +++ b/crates/core/src/node.rs @@ -138,7 +138,10 @@ impl NodeState { ("offset_x", AttributeType::Measure(self.size.offset_x.get())), ("offset_y", AttributeType::Measure(self.size.offset_y.get())), ("content", AttributeType::Content(&self.size.content)), - ("subpixel_rounding", AttributeType::SubpixelRounding(self.style.subpixel_rounding)) + ( + "subpixel_rounding", + AttributeType::SubpixelRounding(self.style.subpixel_rounding), + ), ]; let shadows = &self.style.shadows; diff --git a/crates/state/src/style.rs b/crates/state/src/style.rs index b4963a7c9..7f3bb0989 100644 --- a/crates/state/src/style.rs +++ b/crates/state/src/style.rs @@ -141,7 +141,7 @@ impl ParseAttribute for StyleState { match value { "round" => self.subpixel_rounding = true, "none" => self.subpixel_rounding = false, - _ => return Err(ParseError) + _ => return Err(ParseError), } } } From aae50bdf29347818d345d2aeea7531b59db77d2a Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Sat, 14 Sep 2024 10:59:10 -0500 Subject: [PATCH 5/7] fix: switch alignment --- crates/components/src/switch.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/components/src/switch.rs b/crates/components/src/switch.rs index 106e2c1a9..a071914ba 100644 --- a/crates/components/src/switch.rs +++ b/crates/components/src/switch.rs @@ -174,6 +174,7 @@ pub fn Switch(props: SwitchProps) -> Element { a11y_id, offset_x: "{offset_x}", main_align: "center", + subpixel_rounding: "none", rect { background: "{circle}", width: "{size}", From 8d761f674f6e8b4c0d0ed18c1bf2b201472b9881 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Sat, 14 Sep 2024 12:08:44 -0500 Subject: [PATCH 6/7] fix: remove `main_align` from switch alignment --- crates/components/src/switch.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/components/src/switch.rs b/crates/components/src/switch.rs index a071914ba..266297836 100644 --- a/crates/components/src/switch.rs +++ b/crates/components/src/switch.rs @@ -173,13 +173,12 @@ pub fn Switch(props: SwitchProps) -> Element { onclick, a11y_id, offset_x: "{offset_x}", - main_align: "center", - subpixel_rounding: "none", rect { background: "{circle}", width: "{size}", height: "{size}", corner_radius: "50", + subpixel_rounding: "none", } } ) From b1b9fd1a8d8ca5f1a3f7bb7370dbb1d5b3bcd732 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+Tropix126@users.noreply.github.com> Date: Sat, 14 Sep 2024 12:54:32 -0500 Subject: [PATCH 7/7] fix: pixel snap both switch head and body --- crates/components/src/switch.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/components/src/switch.rs b/crates/components/src/switch.rs index 266297836..359218b96 100644 --- a/crates/components/src/switch.rs +++ b/crates/components/src/switch.rs @@ -173,6 +173,8 @@ pub fn Switch(props: SwitchProps) -> Element { onclick, a11y_id, offset_x: "{offset_x}", + main_align: "center", + subpixel_rounding: "none", rect { background: "{circle}", width: "{size}",