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

feat: Physical pixel snapping #837

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
5 changes: 4 additions & 1 deletion crates/core/src/elements/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<StyleState>().unwrap();
let node_references = node_ref.get::<ReferencesState>().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) });
Expand Down
8 changes: 6 additions & 2 deletions crates/core/src/elements/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<StyleState>().unwrap();
let mut radius = node_style.corner_radius;
radius.scale(scale_factor);
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 6 additions & 1 deletion crates/core/src/elements/svg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<StyleState>().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 {
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down
13 changes: 13 additions & 0 deletions crates/devtools/src/tabs/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
}
})}
}
Expand Down
18 changes: 18 additions & 0 deletions crates/elements/src/_docs/attributes/subpixel_rounding.md
Original file line number Diff line number Diff line change
@@ -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"
}
)
}
```
6 changes: 6 additions & 0 deletions crates/elements/src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions crates/native-core/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub enum AttributeName {
PositionLeft,
Opacity,
Content,
SubpixelRounding,
A11YAutoFocus,
A11YName,
A11YFocusable,
Expand Down Expand Up @@ -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),
Expand Down
36 changes: 35 additions & 1 deletion crates/state/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -43,6 +43,24 @@ pub struct StyleState {
pub image_data: Option<AttributesBytes>,
pub svg_data: Option<AttributesBytes>,
pub overflow: OverflowMode,
pub opacity: Option<f32>,
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 {
Expand Down Expand Up @@ -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::<f32>().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),
}
}
}
_ => {}
}

Expand Down Expand Up @@ -147,6 +179,8 @@ impl State<CustomAttributeValues> for StyleState {
AttributeName::SvgData,
AttributeName::SvgContent,
AttributeName::Overflow,
AttributeName::Opacity,
AttributeName::SubpixelRounding,
]));

fn update<'a>(
Expand Down
1 change: 1 addition & 0 deletions examples/border.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading