Taffy rounding

John Tur created

Change summary

crates/gpui/src/taffy.rs  | 69 +++++++++++-----------------------------
crates/gpui/src/util.rs   | 20 +++++++++++
crates/gpui/src/window.rs | 35 +++++++++++++-------
3 files changed, 62 insertions(+), 62 deletions(-)

Detailed changes

crates/gpui/src/taffy.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     AbsoluteLength, App, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style, Window,
-    point, size,
+    point, size, util::round_device_pixels_midpoint_down,
 };
 use collections::{FxHashMap, FxHashSet};
 use stacksafe::{StackSafe, stacksafe};
@@ -369,16 +369,8 @@ impl ToTaffy<taffy::style::Style> for Style {
 
 impl ToTaffy<f32> for AbsoluteLength {
     fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> f32 {
-        match self {
-            AbsoluteLength::Pixels(pixels) => {
-                let pixels: f32 = pixels.into();
-                pixels * scale_factor
-            }
-            AbsoluteLength::Rems(rems) => {
-                let pixels: f32 = (*rems * rem_size).into();
-                pixels * scale_factor
-            }
-        }
+        let logical_pixels = self.to_pixels(rem_size).0;
+        round_device_pixels_midpoint_down((logical_pixels * scale_factor).max(0.0))
     }
 }
 
@@ -407,16 +399,7 @@ impl ToTaffy<taffy::style::Dimension> for Length {
 impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
     fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
         match self {
-            DefiniteLength::Absolute(length) => match length {
-                AbsoluteLength::Pixels(pixels) => {
-                    let pixels: f32 = pixels.into();
-                    taffy::style::LengthPercentage::length(pixels * scale_factor)
-                }
-                AbsoluteLength::Rems(rems) => {
-                    let pixels: f32 = (*rems * rem_size).into();
-                    taffy::style::LengthPercentage::length(pixels * scale_factor)
-                }
-            },
+            DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
             DefiniteLength::Fraction(fraction) => {
                 taffy::style::LengthPercentage::percent(*fraction)
             }
@@ -427,16 +410,7 @@ impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
 impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
     fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
         match self {
-            DefiniteLength::Absolute(length) => match length {
-                AbsoluteLength::Pixels(pixels) => {
-                    let pixels: f32 = pixels.into();
-                    taffy::style::LengthPercentageAuto::length(pixels * scale_factor)
-                }
-                AbsoluteLength::Rems(rems) => {
-                    let pixels: f32 = (*rems * rem_size).into();
-                    taffy::style::LengthPercentageAuto::length(pixels * scale_factor)
-                }
-            },
+            DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
             DefiniteLength::Fraction(fraction) => {
                 taffy::style::LengthPercentageAuto::percent(*fraction)
             }
@@ -447,15 +421,7 @@ impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
 impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
     fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
         match self {
-            DefiniteLength::Absolute(length) => match length {
-                AbsoluteLength::Pixels(pixels) => {
-                    let pixels: f32 = pixels.into();
-                    taffy::style::Dimension::length(pixels * scale_factor)
-                }
-                AbsoluteLength::Rems(rems) => {
-                    taffy::style::Dimension::length((*rems * rem_size * scale_factor).into())
-                }
-            },
+            DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
             DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
         }
     }
@@ -463,16 +429,19 @@ impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
 
 impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
     fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
-        match self {
-            AbsoluteLength::Pixels(pixels) => {
-                let pixels: f32 = pixels.into();
-                taffy::style::LengthPercentage::length(pixels * scale_factor)
-            }
-            AbsoluteLength::Rems(rems) => {
-                let pixels: f32 = (*rems * rem_size).into();
-                taffy::style::LengthPercentage::length(pixels * scale_factor)
-            }
-        }
+        taffy::style::LengthPercentage::length(self.to_taffy(rem_size, scale_factor))
+    }
+}
+
+impl ToTaffy<taffy::style::LengthPercentageAuto> for AbsoluteLength {
+    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
+        taffy::style::LengthPercentageAuto::length(self.to_taffy(rem_size, scale_factor))
+    }
+}
+
+impl ToTaffy<taffy::style::Dimension> for AbsoluteLength {
+    fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
+        taffy::style::Dimension::length(self.to_taffy(rem_size, scale_factor))
     }
 }
 

crates/gpui/src/util.rs 🔗

@@ -125,12 +125,32 @@ pub(crate) fn atomic_incr_if_not_zero(counter: &AtomicUsize) -> usize {
     }
 }
 
+/// Rounds a device-pixel value to the nearest integer, with .5 ties rounded down.
+#[inline]
+pub(crate) fn round_device_pixels_midpoint_down(value: f32) -> f32 {
+    let floor = value.floor();
+    if value - floor > 0.5 {
+        floor + 1.0
+    } else {
+        floor
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::TestAppContext;
 
     use super::*;
 
+    #[test]
+    fn test_round_device_pixels_midpoint_down() {
+        assert_eq!(round_device_pixels_midpoint_down(0.5), 0.0);
+        assert_eq!(round_device_pixels_midpoint_down(1.5), 1.0);
+        assert_eq!(round_device_pixels_midpoint_down(2.5), 2.0);
+        assert_eq!(round_device_pixels_midpoint_down(1.5001), 2.0);
+        assert_eq!(round_device_pixels_midpoint_down(-1.5), -2.0);
+    }
+
     #[gpui::test]
     async fn test_with_timeout(cx: &mut TestAppContext) {
         Task::ready(())

crates/gpui/src/window.rs 🔗

@@ -57,7 +57,7 @@ use uuid::Uuid;
 
 mod prompts;
 
-use crate::util::atomic_incr_if_not_zero;
+use crate::util::{atomic_incr_if_not_zero, round_device_pixels_midpoint_down};
 pub use prompts::*;
 
 /// Default window size used when no explicit size is provided.
@@ -2118,6 +2118,12 @@ impl Window {
         px((value.0 * scale_factor).round() / scale_factor)
     }
 
+    /// Returns the logical length corresponding to one physical device pixel.
+    #[inline]
+    pub fn one_device_pixel(&self) -> Pixels {
+        px(1.0 / self.scale_factor())
+    }
+
     #[inline]
     fn round_point_to_device_pixels(&self, position: Point<Pixels>) -> Point<Pixels> {
         point(
@@ -2145,11 +2151,16 @@ impl Window {
     #[inline]
     fn round_bounds_to_device_pixels(&self, bounds: Bounds<Pixels>) -> Bounds<ScaledPixels> {
         let scale_factor = self.scale_factor();
-        let left = (bounds.left().0 * scale_factor).round();
-        let top = (bounds.top().0 * scale_factor).round();
-        let right = (bounds.right().0 * scale_factor).round();
-        let bottom = (bounds.bottom().0 * scale_factor).round();
-        self.bounds_from_device_edges(left, top, right, bottom)
+        Bounds {
+            origin: point(
+                ScaledPixels((bounds.origin.x.0 * scale_factor).round()),
+                ScaledPixels((bounds.origin.y.0 * scale_factor).round()),
+            ),
+            size: size(
+                self.round_length_to_device_pixels(bounds.size.width),
+                self.round_length_to_device_pixels(bounds.size.height),
+            ),
+        }
     }
 
     #[inline]
@@ -2174,18 +2185,18 @@ impl Window {
 
     #[inline]
     fn round_edges_to_device_pixels(&self, edges: Edges<Pixels>) -> Edges<ScaledPixels> {
-        let scale_factor = self.scale_factor();
         Edges {
-            top: ScaledPixels((edges.top.0 * scale_factor).round()),
-            right: ScaledPixels((edges.right.0 * scale_factor).round()),
-            bottom: ScaledPixels((edges.bottom.0 * scale_factor).round()),
-            left: ScaledPixels((edges.left.0 * scale_factor).round()),
+            top: self.round_nonzero_length_to_device_pixels(edges.top),
+            right: self.round_nonzero_length_to_device_pixels(edges.right),
+            bottom: self.round_nonzero_length_to_device_pixels(edges.bottom),
+            left: self.round_nonzero_length_to_device_pixels(edges.left),
         }
     }
 
     #[inline]
     fn round_length_to_device_pixels(&self, value: Pixels) -> ScaledPixels {
-        ScaledPixels((value.0 * self.scale_factor()).round())
+        let scaled = (value.0 * self.scale_factor()).max(0.0);
+        ScaledPixels(round_device_pixels_midpoint_down(scaled))
     }
 
     #[inline]