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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 508 additions and 242 deletions

View File

@ -1230,7 +1230,7 @@ egui_extras::install_image_loaders(egui_ctx);
* [Tweaked the default visuals style](https://github.com/emilk/egui/pull/450).
* Plot: Renamed `Curve` to `Line`.
* `TopPanel::top` is now `TopBottomPanel::top`.
* `SidePanel::left` no longet takes the default width by argument, but by a builder call.
* `SidePanel::left` no longer takes the default width by argument, but by a builder call.
* `SidePanel::left` is resizable by default.
### 🐛 Fixed

View File

@ -8,7 +8,9 @@ use crate::{
TextStyle, Ui, UiKind, Vec2b, WidgetInfo, WidgetRect, WidgetText, WidgetType,
};
use emath::GuiRounding as _;
use epaint::{emath, pos2, vec2, Galley, Pos2, Rect, RectShape, Rounding, Shape, Stroke, Vec2};
use epaint::{
emath, pos2, vec2, Galley, Pos2, Rect, RectShape, Rounding, Roundingf, Shape, Stroke, Vec2,
};
use super::scroll_area::ScrollBarVisibility;
use super::{area, resize, Area, Frame, Resize, ScrollArea};
@ -486,8 +488,9 @@ impl<'open> Window<'open> {
let style = ctx.style();
let spacing = window_margin.top + window_margin.bottom;
let height = ctx.fonts(|f| title.font_height(f, &style)) + spacing;
window_frame.rounding.ne = window_frame.rounding.ne.clamp(0.0, height / 2.0);
window_frame.rounding.nw = window_frame.rounding.nw.clamp(0.0, height / 2.0);
let half_height = (height / 2.0).round() as _;
window_frame.rounding.ne = window_frame.rounding.ne.clamp(0, half_height);
window_frame.rounding.nw = window_frame.rounding.nw.clamp(0, half_height);
(height, spacing)
} else {
(0.0, 0.0)
@ -603,8 +606,8 @@ impl<'open> Window<'open> {
let mut round = window_frame.rounding;
if !is_collapsed {
round.se = 0.0;
round.sw = 0.0;
round.se = 0;
round.sw = 0;
}
area_content_ui.painter().set(
@ -682,6 +685,7 @@ fn paint_resize_corner(
};
// Adjust the corner offset to accommodate for window rounding
let radius = radius as f32;
let offset =
((2.0_f32.sqrt() * (1.0 + radius) - radius) * 45.0_f32.to_radians().cos()).max(2.0);
@ -1022,7 +1026,7 @@ fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction)
bottom = interaction.bottom.hover;
}
let rounding = ui.visuals().window_rounding;
let rounding = Roundingf::from(ui.visuals().window_rounding);
let Rect { min, max } = rect;
let mut points = Vec::new();

View File

@ -1291,7 +1291,7 @@ impl Visuals {
warn_fg_color: Color32::from_rgb(255, 143, 0), // orange
error_fg_color: Color32::from_rgb(255, 0, 0), // red
window_rounding: Rounding::same(6.0),
window_rounding: Rounding::same(6),
window_shadow: Shadow {
offset: vec2(10.0, 20.0),
blur: 15.0,
@ -1302,7 +1302,7 @@ impl Visuals {
window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
window_highlight_topmost: true,
menu_rounding: Rounding::same(6.0),
menu_rounding: Rounding::same(6),
panel_fill: Color32::from_gray(27),
@ -1412,7 +1412,7 @@ impl Widgets {
bg_fill: Color32::from_gray(27),
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines
fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
rounding: Rounding::same(2.0),
rounding: Rounding::same(2),
expansion: 0.0,
},
inactive: WidgetVisuals {
@ -1420,7 +1420,7 @@ impl Widgets {
bg_fill: Color32::from_gray(60), // checkbox background
bg_stroke: Default::default(),
fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
rounding: Rounding::same(2.0),
rounding: Rounding::same(2),
expansion: 0.0,
},
hovered: WidgetVisuals {
@ -1428,7 +1428,7 @@ impl Widgets {
bg_fill: Color32::from_gray(70),
bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
rounding: Rounding::same(3.0),
rounding: Rounding::same(3),
expansion: 1.0,
},
active: WidgetVisuals {
@ -1436,7 +1436,7 @@ impl Widgets {
bg_fill: Color32::from_gray(55),
bg_stroke: Stroke::new(1.0, Color32::WHITE),
fg_stroke: Stroke::new(2.0, Color32::WHITE),
rounding: Rounding::same(2.0),
rounding: Rounding::same(2),
expansion: 1.0,
},
open: WidgetVisuals {
@ -1444,7 +1444,7 @@ impl Widgets {
bg_fill: Color32::from_gray(27),
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
rounding: Rounding::same(2.0),
rounding: Rounding::same(2),
expansion: 0.0,
},
}
@ -1457,7 +1457,7 @@ impl Widgets {
bg_fill: Color32::from_gray(248),
bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), // separators, indentation lines
fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), // normal text color
rounding: Rounding::same(2.0),
rounding: Rounding::same(2),
expansion: 0.0,
},
inactive: WidgetVisuals {
@ -1465,7 +1465,7 @@ impl Widgets {
bg_fill: Color32::from_gray(230), // checkbox background
bg_stroke: Default::default(),
fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // button text
rounding: Rounding::same(2.0),
rounding: Rounding::same(2),
expansion: 0.0,
},
hovered: WidgetVisuals {
@ -1473,7 +1473,7 @@ impl Widgets {
bg_fill: Color32::from_gray(220),
bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button
fg_stroke: Stroke::new(1.5, Color32::BLACK),
rounding: Rounding::same(3.0),
rounding: Rounding::same(3),
expansion: 1.0,
},
active: WidgetVisuals {
@ -1481,7 +1481,7 @@ impl Widgets {
bg_fill: Color32::from_gray(165),
bg_stroke: Stroke::new(1.0, Color32::BLACK),
fg_stroke: Stroke::new(2.0, Color32::BLACK),
rounding: Rounding::same(2.0),
rounding: Rounding::same(2),
expansion: 1.0,
},
open: WidgetVisuals {
@ -1489,7 +1489,7 @@ impl Widgets {
bg_fill: Color32::from_gray(220),
bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
fg_stroke: Stroke::new(1.0, Color32::BLACK),
rounding: Rounding::same(2.0),
rounding: Rounding::same(2),
expansion: 0.0,
},
}
@ -2420,9 +2420,16 @@ impl Widget for &mut Rounding {
// Apply the checkbox:
if same {
*self = Rounding::same((self.nw + self.ne + self.sw + self.se) / 4.0);
} else if self.is_same() {
self.se *= 1.00001; // prevent collapsing into sameness
*self = Rounding::from(self.average());
} else {
// Make sure we aren't same:
if self.is_same() {
if self.average() == 0.0 {
self.se = 1;
} else {
self.se -= 1;
}
}
}
response

View File

@ -99,7 +99,7 @@ fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response {
show_color_at(ui.painter(), color, rect);
let rounding = visuals.rounding.at_most(2.0); // Can't do more rounding because the background grid doesn't do any rounding
let rounding = visuals.rounding.at_most(2); // Can't do more rounding because the background grid doesn't do any rounding
ui.painter()
.rect_stroke(rect, rounding, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border
}

View File

@ -138,7 +138,8 @@ impl Widget for ProgressBar {
let rounding = rounding.unwrap_or_else(|| corner_radius.into());
ui.painter()
.rect(outer_rect, rounding, visuals.extreme_bg_color, Stroke::NONE);
let min_width = 2.0 * rounding.sw.at_least(rounding.nw).at_most(corner_radius);
let min_width =
2.0 * f32::max(rounding.sw as _, rounding.nw as _).at_most(corner_radius);
let filled_width = (outer_rect.width() * progress).at_least(min_width);
let inner_rect =
Rect::from_min_size(outer_rect.min, vec2(filled_width, outer_rect.height()));

View File

@ -780,10 +780,10 @@ impl<'a> Slider<'a> {
// The trailing rect has to be drawn differently depending on the orientation.
match self.orientation {
SliderOrientation::Horizontal => {
trailing_rail_rect.max.x = center.x + rounding.nw;
trailing_rail_rect.max.x = center.x + rounding.nw as f32;
}
SliderOrientation::Vertical => {
trailing_rail_rect.min.y = center.y - rounding.se;
trailing_rail_rect.min.y = center.y - rounding.se as f32;
}
};

View File

@ -127,7 +127,7 @@ impl crate::View for PanZoom {
.show(ui.ctx(), |ui| {
ui.set_clip_rect(transform.inverse() * rect);
egui::Frame::default()
.rounding(egui::Rounding::same(4.0))
.rounding(egui::Rounding::same(4))
.inner_margin(egui::Margin::same(8.0))
.stroke(ui.ctx().style().visuals.window_stroke)
.fill(ui.style().visuals.panel_fill)

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);
}