egui/crates/emath/src/gui_rounding.rs

203 lines
6.7 KiB
Rust

/// We (sometimes) round sizes and coordinates to an even multiple of this value.
///
/// This is only used for rounding _logical UI points_, used for widget coordinates and sizes.
/// When rendering, you may want to round to an integer multiple of the physical _pixels_ instead,
/// using [`GuiRounding::round_to_pixels`].
///
/// See [`GuiRounding::round_ui`] for more information.
///
/// This constant has to be a (negative) power of two so that it can be represented exactly
/// by a floating point number.
///
/// If we pick too large a value (e.g. 1 or 1/2), then we get judder during scrolling and animations.
/// If we pick too small a value (e.g. 1/4096), we run the risk of rounding errors again.
///
/// `f32` has 23 bits of mantissa, so if we use e.g. 1/8 as the rounding factor,
/// we can represent all numbers up to 2^20 exactly, which is plenty
/// (to my knowledge there are no displays that are a million pixels wide).
pub const GUI_ROUNDING: f32 = 1.0 / 32.0;
/// Trait for rounding coordinates and sizes to align with either .
///
/// See [`GuiRounding::round_ui`] for more information.
pub trait GuiRounding {
/// Rounds floating point numbers to an even multiple of the GUI rounding factor, [`crate::GUI_ROUNDING`].
///
/// Use this for widget coordinates and sizes.
///
/// Rounding sizes and positions prevent rounding errors when doing sizing calculations.
/// We don't round to integers, because that would be too coarse (causing visible juddering when scrolling, for instance).
/// Instead we round to an even multiple of [`GUI_ROUNDING`].
fn round_ui(self) -> Self;
/// Like [`Self::round_ui`], but always rounds towards negative infinity.
fn floor_ui(self) -> Self;
/// Round a size or position to an even multiple of the physical pixel size.
///
/// This can be useful for crisp rendering.
///
/// The `self` should be in coordinates of _logical UI points_.
/// The argument `pixels_per_point` is the number of _physical pixels_ per logical UI point.
/// For instance, on a high-DPI screen, `pixels_per_point` could be `2.0`.
fn round_to_pixels(self, pixels_per_point: f32) -> Self;
/// Will round the position to be in the center of a pixel.
///
/// The pixel size is `1.0 / pixels_per_point`.
///
/// So if `pixels_per_point = 2` (i.e. `pixel size = 0.5`),
/// then the position will be rounded to the closest of `…, 0.25, 0.75, 1.25, …`.
///
/// This is useful, for instance, when picking the center of a line that is one pixel wide.
fn round_to_pixel_center(self, pixels_per_point: f32) -> Self;
}
impl GuiRounding for f32 {
#[inline]
fn round_ui(self) -> Self {
(self / GUI_ROUNDING).round() * GUI_ROUNDING
}
#[inline]
fn floor_ui(self) -> Self {
(self / GUI_ROUNDING).floor() * GUI_ROUNDING
}
#[inline]
fn round_to_pixels(self, pixels_per_point: f32) -> Self {
(self * pixels_per_point).round() / pixels_per_point
}
#[inline]
fn round_to_pixel_center(self, pixels_per_point: f32) -> Self {
((self * pixels_per_point - 0.5).round() + 0.5) / pixels_per_point
}
}
impl GuiRounding for f64 {
#[inline]
fn round_ui(self) -> Self {
(self / GUI_ROUNDING as Self).round() * GUI_ROUNDING as Self
}
#[inline]
fn floor_ui(self) -> Self {
(self / GUI_ROUNDING as Self).floor() * GUI_ROUNDING as Self
}
#[inline]
fn round_to_pixels(self, pixels_per_point: f32) -> Self {
(self * pixels_per_point as Self).round() / pixels_per_point as Self
}
#[inline]
fn round_to_pixel_center(self, pixels_per_point: f32) -> Self {
((self * pixels_per_point as Self - 0.5).round() + 0.5) / pixels_per_point as Self
}
}
impl GuiRounding for crate::Vec2 {
#[inline]
fn round_ui(self) -> Self {
Self::new(self.x.round_ui(), self.y.round_ui())
}
#[inline]
fn floor_ui(self) -> Self {
Self::new(self.x.floor_ui(), self.y.floor_ui())
}
#[inline]
fn round_to_pixels(self, pixels_per_point: f32) -> Self {
Self::new(
self.x.round_to_pixels(pixels_per_point),
self.y.round_to_pixels(pixels_per_point),
)
}
// This doesn't really make sense for a Vec2, but 🤷‍♂️
#[inline]
fn round_to_pixel_center(self, pixels_per_point: f32) -> Self {
Self::new(
self.x.round_to_pixel_center(pixels_per_point),
self.y.round_to_pixel_center(pixels_per_point),
)
}
}
impl GuiRounding for crate::Pos2 {
#[inline]
fn round_ui(self) -> Self {
Self::new(self.x.round_ui(), self.y.round_ui())
}
#[inline]
fn floor_ui(self) -> Self {
Self::new(self.x.floor_ui(), self.y.floor_ui())
}
#[inline]
fn round_to_pixels(self, pixels_per_point: f32) -> Self {
Self::new(
self.x.round_to_pixels(pixels_per_point),
self.y.round_to_pixels(pixels_per_point),
)
}
#[inline]
fn round_to_pixel_center(self, pixels_per_point: f32) -> Self {
Self::new(
self.x.round_to_pixel_center(pixels_per_point),
self.y.round_to_pixel_center(pixels_per_point),
)
}
}
impl GuiRounding for crate::Rect {
/// Rounded so that two adjacent rects that tile perfectly
/// will continue to tile perfectly.
#[inline]
fn round_ui(self) -> Self {
Self::from_min_max(self.min.round_ui(), self.max.round_ui())
}
/// Rounded so that two adjacent rects that tile perfectly
/// will continue to tile perfectly.
#[inline]
fn floor_ui(self) -> Self {
Self::from_min_max(self.min.floor_ui(), self.max.floor_ui())
}
/// Rounded so that two adjacent rects that tile perfectly
/// will continue to tile perfectly.
#[inline]
fn round_to_pixels(self, pixels_per_point: f32) -> Self {
Self::from_min_max(
self.min.round_to_pixels(pixels_per_point),
self.max.round_to_pixels(pixels_per_point),
)
}
/// Rounded so that two adjacent rects that tile perfectly
/// will continue to tile perfectly.
#[inline]
fn round_to_pixel_center(self, pixels_per_point: f32) -> Self {
Self::from_min_max(
self.min.round_to_pixel_center(pixels_per_point),
self.max.round_to_pixel_center(pixels_per_point),
)
}
}
#[test]
fn test_gui_rounding() {
assert_eq!(0.0_f32.round_ui(), 0.0);
assert_eq!((GUI_ROUNDING * 1.11).round_ui(), GUI_ROUNDING);
assert_eq!((-GUI_ROUNDING * 1.11).round_ui(), -GUI_ROUNDING);
assert_eq!(f32::NEG_INFINITY.round_ui(), f32::NEG_INFINITY);
assert_eq!(f32::INFINITY.round_ui(), f32::INFINITY);
assert_eq!(0.17_f32.round_to_pixel_center(2.0), 0.25);
}