diff --git a/crates/components/src/switch.rs b/crates/components/src/switch.rs index 106e2c1a9..359218b96 100644 --- a/crates/components/src/switch.rs +++ b/crates/components/src/switch.rs @@ -174,11 +174,13 @@ pub fn Switch(props: SwitchProps) -> Element { 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", } } ) diff --git a/crates/core/src/elements/image.rs b/crates/core/src/elements/image.rs index bbc49c111..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(); 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/rect.rs b/crates/core/src/elements/rect.rs index 3be23687c..58b7befee 100644 --- a/crates/core/src/elements/rect.rs +++ b/crates/core/src/elements/rect.rs @@ -33,7 +33,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); @@ -89,7 +89,11 @@ impl ElementUtils for RectElement { let mut paint = Paint::default(); let mut path = Path::new(); - let area = layout_node.visible_area().to_f32(); + 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 e0685d816..570550531 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(); 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/core/src/node.rs b/crates/core/src/node.rs index 11509017d..c80f837e8 100644 --- a/crates/core/src/node.rs +++ b/crates/core/src/node.rs @@ -138,6 +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), + ), ]; let shadows = &self.style.shadows; @@ -169,6 +173,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 d99e75e06..be93fec50 100644 --- a/crates/devtools/src/tabs/style.rs +++ b/crates/devtools/src/tabs/style.rs @@ -173,6 +173,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/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 9ee27960a..07de14020 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, #[doc = include_str!("_docs/attributes/spacing.md")] spacing: String, @@ -454,6 +456,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, @@ -494,6 +498,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/native-core/src/attributes.rs b/crates/native-core/src/attributes.rs index 6f248ac65..689f56bd0 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, A11YAutoFocus, A11YName, A11YFocusable, @@ -117,6 +118,7 @@ impl FromStr for AttributeName { "position_left" => Ok(AttributeName::PositionLeft), "opacity" => Ok(AttributeName::Opacity), "content" => Ok(AttributeName::Content), + "subpixel_rounding" => Ok(AttributeName::SubpixelRounding), "a11y_auto_focus" => Ok(AttributeName::A11YAutoFocus), "a11y_name" => Ok(AttributeName::A11YName), "a11y_focusable" => Ok(AttributeName::A11YFocusable), diff --git a/crates/state/src/style.rs b/crates/state/src/style.rs index defaa60d3..d95f92e22 100644 --- a/crates/state/src/style.rs +++ b/crates/state/src/style.rs @@ -34,7 +34,7 @@ use crate::{ Shadow, }; -#[derive(Default, Debug, Clone, PartialEq, Component)] +#[derive(Debug, Clone, PartialEq, Component)] pub struct StyleState { pub background: Fill, pub border: Border, @@ -43,6 +43,24 @@ pub struct StyleState { pub image_data: Option, pub svg_data: Option, pub overflow: OverflowMode, + pub opacity: Option, + 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 { @@ -119,6 +137,20 @@ impl ParseAttribute for StyleState { self.overflow = OverflowMode::parse(value)?; } } + AttributeName::Opacity => { + if let Some(value) = attr.value.as_text() { + 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, + _ => return Err(ParseError), + } + } + } _ => {} } @@ -147,6 +179,8 @@ impl State for StyleState { AttributeName::SvgData, 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",