1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 13:20:05 -04:00

Use u8 in Rounding, and introduce Roundingf (#5563)

* Part of https://github.com/emilk/egui/issues/4019

As part of the work on adding a custom `Border` to everything, I want to
make sure that the size of `RectShape`, `Frame` and the future `Border`
is kept small (for performance reasons).

This PR changes the storage of the corner radius of rectangles from four
`f32` (one for each corner) into four `u8`. This mean the corner radius
can only be an integer in the range 0-255 (in ui points). This should be
enough for most people.

If you want to manipulate rounding using `f32`, there is a new
`Roundingf` to fill that niche.
This commit is contained in:
Emil Ernerfeldt
2025-01-02 14:29:50 +01:00
committed by GitHub
parent 3ffe1ed774
commit 249f8bcb93
13 changed files with 508 additions and 242 deletions

View File

@@ -29,6 +29,8 @@ pub mod image;
mod margin;
mod mesh;
pub mod mutex;
mod rounding;
mod roundingf;
mod shadow;
mod shape;
pub mod shape_transform;
@@ -47,10 +49,12 @@ pub use self::{
image::{ColorImage, FontImage, ImageData, ImageDelta},
margin::Margin,
mesh::{Mesh, Mesh16, Vertex},
rounding::Rounding,
roundingf::Roundingf,
shadow::Shadow,
shape::{
CircleShape, EllipseShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape,
Rounding, Shape, TextShape,
CircleShape, EllipseShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape, Shape,
TextShape,
},
stats::PaintStats,
stroke::{PathStroke, Stroke, StrokeKind},

View File

@@ -0,0 +1,220 @@
/// How rounded the corners of things should be.
///
/// The rounding uses `u8` to save space,
/// so the amount of rounding is limited to integers in the range `[0, 255]`.
///
/// For calculations, you may want to use [`crate::Roundingf`] instead, which uses `f32`.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Rounding {
/// Radius of the rounding of the North-West (left top) corner.
pub nw: u8,
/// Radius of the rounding of the North-East (right top) corner.
pub ne: u8,
/// Radius of the rounding of the South-West (left bottom) corner.
pub sw: u8,
/// Radius of the rounding of the South-East (right bottom) corner.
pub se: u8,
}
impl Default for Rounding {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl From<u8> for Rounding {
#[inline]
fn from(radius: u8) -> Self {
Self::same(radius)
}
}
impl From<f32> for Rounding {
#[inline]
fn from(radius: f32) -> Self {
Self::same(radius.round() as u8)
}
}
impl Rounding {
/// No rounding on any corner.
pub const ZERO: Self = Self {
nw: 0,
ne: 0,
sw: 0,
se: 0,
};
/// Same rounding on all four corners.
#[inline]
pub const fn same(radius: u8) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
/// Do all corners have the same rounding?
#[inline]
pub fn is_same(self) -> bool {
self.nw == self.ne && self.nw == self.sw && self.nw == self.se
}
/// Make sure each corner has a rounding of at least this.
#[inline]
pub fn at_least(self, min: u8) -> Self {
Self {
nw: self.nw.max(min),
ne: self.ne.max(min),
sw: self.sw.max(min),
se: self.se.max(min),
}
}
/// Make sure each corner has a rounding of at most this.
#[inline]
pub fn at_most(self, max: u8) -> Self {
Self {
nw: self.nw.min(max),
ne: self.ne.min(max),
sw: self.sw.min(max),
se: self.se.min(max),
}
}
/// Average rounding of the corners.
pub fn average(&self) -> f32 {
(self.nw as f32 + self.ne as f32 + self.sw as f32 + self.se as f32) / 4.0
}
}
impl std::ops::Add for Rounding {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self {
nw: self.nw + rhs.nw,
ne: self.ne + rhs.ne,
sw: self.sw + rhs.sw,
se: self.se + rhs.se,
}
}
}
impl std::ops::AddAssign for Rounding {
#[inline]
fn add_assign(&mut self, rhs: Self) {
*self = Self {
nw: self.nw + rhs.nw,
ne: self.ne + rhs.ne,
sw: self.sw + rhs.sw,
se: self.se + rhs.se,
};
}
}
impl std::ops::AddAssign<u8> for Rounding {
#[inline]
fn add_assign(&mut self, rhs: u8) {
*self = Self {
nw: self.nw.saturating_add(rhs),
ne: self.ne.saturating_add(rhs),
sw: self.sw.saturating_add(rhs),
se: self.se.saturating_add(rhs),
};
}
}
impl std::ops::Sub for Rounding {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self {
nw: self.nw.saturating_sub(rhs.nw),
ne: self.ne.saturating_sub(rhs.ne),
sw: self.sw.saturating_sub(rhs.sw),
se: self.se.saturating_sub(rhs.se),
}
}
}
impl std::ops::SubAssign for Rounding {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
*self = Self {
nw: self.nw.saturating_sub(rhs.nw),
ne: self.ne.saturating_sub(rhs.ne),
sw: self.sw.saturating_sub(rhs.sw),
se: self.se.saturating_sub(rhs.se),
};
}
}
impl std::ops::SubAssign<u8> for Rounding {
#[inline]
fn sub_assign(&mut self, rhs: u8) {
*self = Self {
nw: self.nw.saturating_sub(rhs),
ne: self.ne.saturating_sub(rhs),
sw: self.sw.saturating_sub(rhs),
se: self.se.saturating_sub(rhs),
};
}
}
impl std::ops::Div<f32> for Rounding {
type Output = Self;
#[inline]
fn div(self, rhs: f32) -> Self {
Self {
nw: (self.nw as f32 / rhs) as u8,
ne: (self.ne as f32 / rhs) as u8,
sw: (self.sw as f32 / rhs) as u8,
se: (self.se as f32 / rhs) as u8,
}
}
}
impl std::ops::DivAssign<f32> for Rounding {
#[inline]
fn div_assign(&mut self, rhs: f32) {
*self = Self {
nw: (self.nw as f32 / rhs) as u8,
ne: (self.ne as f32 / rhs) as u8,
sw: (self.sw as f32 / rhs) as u8,
se: (self.se as f32 / rhs) as u8,
};
}
}
impl std::ops::Mul<f32> for Rounding {
type Output = Self;
#[inline]
fn mul(self, rhs: f32) -> Self {
Self {
nw: (self.nw as f32 * rhs) as u8,
ne: (self.ne as f32 * rhs) as u8,
sw: (self.sw as f32 * rhs) as u8,
se: (self.se as f32 * rhs) as u8,
}
}
}
impl std::ops::MulAssign<f32> for Rounding {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
*self = Self {
nw: (self.nw as f32 * rhs) as u8,
ne: (self.ne as f32 * rhs) as u8,
sw: (self.sw as f32 * rhs) as u8,
se: (self.se as f32 * rhs) as u8,
};
}
}

View File

@@ -0,0 +1,236 @@
use crate::Rounding;
/// How rounded the corners of things should be, in `f32`.
///
/// This is used for calculations, but storage is usually done with the more compact [`Rounding`].
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Roundingf {
/// Radius of the rounding of the North-West (left top) corner.
pub nw: f32,
/// Radius of the rounding of the North-East (right top) corner.
pub ne: f32,
/// Radius of the rounding of the South-West (left bottom) corner.
pub sw: f32,
/// Radius of the rounding of the South-East (right bottom) corner.
pub se: f32,
}
impl From<Rounding> for Roundingf {
#[inline]
fn from(rounding: Rounding) -> Self {
Self {
nw: rounding.nw as f32,
ne: rounding.ne as f32,
sw: rounding.sw as f32,
se: rounding.se as f32,
}
}
}
impl From<Roundingf> for Rounding {
#[inline]
fn from(rounding: Roundingf) -> Self {
Self {
nw: rounding.nw.round() as u8,
ne: rounding.ne.round() as u8,
sw: rounding.sw.round() as u8,
se: rounding.se.round() as u8,
}
}
}
impl Default for Roundingf {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl From<f32> for Roundingf {
#[inline]
fn from(radius: f32) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
}
impl Roundingf {
/// No rounding on any corner.
pub const ZERO: Self = Self {
nw: 0.0,
ne: 0.0,
sw: 0.0,
se: 0.0,
};
/// Same rounding on all four corners.
#[inline]
pub const fn same(radius: f32) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
/// Do all corners have the same rounding?
#[inline]
pub fn is_same(&self) -> bool {
self.nw == self.ne && self.nw == self.sw && self.nw == self.se
}
/// Make sure each corner has a rounding of at least this.
#[inline]
pub fn at_least(&self, min: f32) -> Self {
Self {
nw: self.nw.max(min),
ne: self.ne.max(min),
sw: self.sw.max(min),
se: self.se.max(min),
}
}
/// Make sure each corner has a rounding of at most this.
#[inline]
pub fn at_most(&self, max: f32) -> Self {
Self {
nw: self.nw.min(max),
ne: self.ne.min(max),
sw: self.sw.min(max),
se: self.se.min(max),
}
}
}
impl std::ops::Add for Roundingf {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self {
nw: self.nw + rhs.nw,
ne: self.ne + rhs.ne,
sw: self.sw + rhs.sw,
se: self.se + rhs.se,
}
}
}
impl std::ops::AddAssign for Roundingf {
#[inline]
fn add_assign(&mut self, rhs: Self) {
*self = Self {
nw: self.nw + rhs.nw,
ne: self.ne + rhs.ne,
sw: self.sw + rhs.sw,
se: self.se + rhs.se,
};
}
}
impl std::ops::AddAssign<f32> for Roundingf {
#[inline]
fn add_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw + rhs,
ne: self.ne + rhs,
sw: self.sw + rhs,
se: self.se + rhs,
};
}
}
impl std::ops::Sub for Roundingf {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self {
nw: self.nw - rhs.nw,
ne: self.ne - rhs.ne,
sw: self.sw - rhs.sw,
se: self.se - rhs.se,
}
}
}
impl std::ops::SubAssign for Roundingf {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
*self = Self {
nw: self.nw - rhs.nw,
ne: self.ne - rhs.ne,
sw: self.sw - rhs.sw,
se: self.se - rhs.se,
};
}
}
impl std::ops::SubAssign<f32> for Roundingf {
#[inline]
fn sub_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw - rhs,
ne: self.ne - rhs,
sw: self.sw - rhs,
se: self.se - rhs,
};
}
}
impl std::ops::Div<f32> for Roundingf {
type Output = Self;
#[inline]
fn div(self, rhs: f32) -> Self {
Self {
nw: self.nw / rhs,
ne: self.ne / rhs,
sw: self.sw / rhs,
se: self.se / rhs,
}
}
}
impl std::ops::DivAssign<f32> for Roundingf {
#[inline]
fn div_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw / rhs,
ne: self.ne / rhs,
sw: self.sw / rhs,
se: self.se / rhs,
};
}
}
impl std::ops::Mul<f32> for Roundingf {
type Output = Self;
#[inline]
fn mul(self, rhs: f32) -> Self {
Self {
nw: self.nw * rhs,
ne: self.ne * rhs,
sw: self.sw * rhs,
se: self.se * rhs,
}
}
}
impl std::ops::MulAssign<f32> for Roundingf {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw * rhs,
ne: self.ne * rhs,
sw: self.sw * rhs,
se: self.se * rhs,
};
}
}

View File

@@ -47,7 +47,7 @@ impl Shadow {
} = *self;
let rect = rect.translate(offset).expand(spread);
let rounding = rounding.into() + Rounding::same(spread.abs());
let rounding = rounding.into() + Rounding::from(spread.abs());
RectShape::filled(rect, rounding, color).with_blur_width(blur)
}

View File

@@ -5,7 +5,7 @@ use std::{any::Any, sync::Arc};
use crate::{
stroke::PathStroke,
text::{FontId, Fonts, Galley},
Color32, Mesh, Stroke, TextureId,
Color32, Mesh, Rounding, Stroke, TextureId,
};
use emath::{pos2, Align2, Pos2, Rangef, Rect, TSTransform, Vec2};
@@ -779,214 +779,6 @@ impl From<RectShape> for Shape {
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
/// How rounded the corners of things should be
pub struct Rounding {
/// Radius of the rounding of the North-West (left top) corner.
pub nw: f32,
/// Radius of the rounding of the North-East (right top) corner.
pub ne: f32,
/// Radius of the rounding of the South-West (left bottom) corner.
pub sw: f32,
/// Radius of the rounding of the South-East (right bottom) corner.
pub se: f32,
}
impl Default for Rounding {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl From<f32> for Rounding {
#[inline]
fn from(radius: f32) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
}
impl Rounding {
/// No rounding on any corner.
pub const ZERO: Self = Self {
nw: 0.0,
ne: 0.0,
sw: 0.0,
se: 0.0,
};
#[inline]
pub const fn same(radius: f32) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
/// Do all corners have the same rounding?
#[inline]
pub fn is_same(&self) -> bool {
self.nw == self.ne && self.nw == self.sw && self.nw == self.se
}
/// Make sure each corner has a rounding of at least this.
#[inline]
pub fn at_least(&self, min: f32) -> Self {
Self {
nw: self.nw.max(min),
ne: self.ne.max(min),
sw: self.sw.max(min),
se: self.se.max(min),
}
}
/// Make sure each corner has a rounding of at most this.
#[inline]
pub fn at_most(&self, max: f32) -> Self {
Self {
nw: self.nw.min(max),
ne: self.ne.min(max),
sw: self.sw.min(max),
se: self.se.min(max),
}
}
}
impl std::ops::Add for Rounding {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self {
nw: self.nw + rhs.nw,
ne: self.ne + rhs.ne,
sw: self.sw + rhs.sw,
se: self.se + rhs.se,
}
}
}
impl std::ops::AddAssign for Rounding {
#[inline]
fn add_assign(&mut self, rhs: Self) {
*self = Self {
nw: self.nw + rhs.nw,
ne: self.ne + rhs.ne,
sw: self.sw + rhs.sw,
se: self.se + rhs.se,
};
}
}
impl std::ops::AddAssign<f32> for Rounding {
#[inline]
fn add_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw + rhs,
ne: self.ne + rhs,
sw: self.sw + rhs,
se: self.se + rhs,
};
}
}
impl std::ops::Sub for Rounding {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self {
nw: self.nw - rhs.nw,
ne: self.ne - rhs.ne,
sw: self.sw - rhs.sw,
se: self.se - rhs.se,
}
}
}
impl std::ops::SubAssign for Rounding {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
*self = Self {
nw: self.nw - rhs.nw,
ne: self.ne - rhs.ne,
sw: self.sw - rhs.sw,
se: self.se - rhs.se,
};
}
}
impl std::ops::SubAssign<f32> for Rounding {
#[inline]
fn sub_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw - rhs,
ne: self.ne - rhs,
sw: self.sw - rhs,
se: self.se - rhs,
};
}
}
impl std::ops::Div<f32> for Rounding {
type Output = Self;
#[inline]
fn div(self, rhs: f32) -> Self {
Self {
nw: self.nw / rhs,
ne: self.ne / rhs,
sw: self.sw / rhs,
se: self.se / rhs,
}
}
}
impl std::ops::DivAssign<f32> for Rounding {
#[inline]
fn div_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw / rhs,
ne: self.ne / rhs,
sw: self.sw / rhs,
se: self.se / rhs,
};
}
}
impl std::ops::Mul<f32> for Rounding {
type Output = Self;
#[inline]
fn mul(self, rhs: f32) -> Self {
Self {
nw: self.nw * rhs,
ne: self.ne * rhs,
sw: self.sw * rhs,
se: self.se * rhs,
}
}
}
impl std::ops::MulAssign<f32> for Rounding {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw * rhs,
ne: self.ne * rhs,
sw: self.sw * rhs,
se: self.se * rhs,
};
}
}
// ----------------------------------------------------------------------------
/// How to paint some text on screen.

View File

@@ -525,7 +525,7 @@ impl Path {
pub mod path {
//! Helpers for constructing paths
use crate::shape::Rounding;
use crate::Rounding;
use emath::{pos2, Pos2, Rect};
/// overwrites existing points
@@ -548,6 +548,8 @@ pub mod path {
// Duplicated vertices can happen when one side is all rounding, with no straight edge between.
let eps = f32::EPSILON * rect.size().max_elem();
let r = crate::Roundingf::from(r);
add_circle_quadrant(path, pos2(max.x - r.se, max.y - r.se), r.se, 0.0); // south east
if rect.width() <= r.se + r.sw + eps {
@@ -628,7 +630,7 @@ pub mod path {
let half_width = rect.width() * 0.5;
let half_height = rect.height() * 0.5;
let max_cr = half_width.min(half_height);
rounding.at_most(max_cr).at_least(0.0)
rounding.at_most(max_cr.floor() as _).at_least(0)
}
}
@@ -1741,7 +1743,7 @@ impl Tessellator {
.at_most(rect.size().min_elem() - eps)
.at_least(0.0);
rounding += Rounding::same(0.5 * blur_width);
rounding += Rounding::from(0.5 * blur_width);
self.feathering = self.feathering.max(blur_width);
}