From 249f8bcb9361495fe9245919aa1b16bca424b52b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 Jan 2025 14:29:50 +0100 Subject: [PATCH] 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. --- CHANGELOG.md | 2 +- crates/egui/src/containers/window.rs | 16 +- crates/egui/src/style.rs | 37 ++-- crates/egui/src/widgets/color_picker.rs | 2 +- crates/egui/src/widgets/progress_bar.rs | 3 +- crates/egui/src/widgets/slider.rs | 4 +- crates/egui_demo_lib/src/demo/pan_zoom.rs | 2 +- crates/epaint/src/lib.rs | 8 +- crates/epaint/src/rounding.rs | 220 ++++++++++++++++++++ crates/epaint/src/roundingf.rs | 236 ++++++++++++++++++++++ crates/epaint/src/shadow.rs | 2 +- crates/epaint/src/shape.rs | 210 +------------------ crates/epaint/src/tessellator.rs | 8 +- 13 files changed, 508 insertions(+), 242 deletions(-) create mode 100644 crates/epaint/src/rounding.rs create mode 100644 crates/epaint/src/roundingf.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4642da63..b6733405 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index c48f6009..f1c67300 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -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(); diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 33ab21a6..af35972c 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -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 diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index 9a605905..ea1b53ae 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -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 } diff --git a/crates/egui/src/widgets/progress_bar.rs b/crates/egui/src/widgets/progress_bar.rs index 5e099f6a..7f926d02 100644 --- a/crates/egui/src/widgets/progress_bar.rs +++ b/crates/egui/src/widgets/progress_bar.rs @@ -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())); diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 7dc3a5cd..71f4c849 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -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; } }; diff --git a/crates/egui_demo_lib/src/demo/pan_zoom.rs b/crates/egui_demo_lib/src/demo/pan_zoom.rs index f8411a74..421dc521 100644 --- a/crates/egui_demo_lib/src/demo/pan_zoom.rs +++ b/crates/egui_demo_lib/src/demo/pan_zoom.rs @@ -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) diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index b769275c..1cc9e6c9 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -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}, diff --git a/crates/epaint/src/rounding.rs b/crates/epaint/src/rounding.rs new file mode 100644 index 00000000..12695f38 --- /dev/null +++ b/crates/epaint/src/rounding.rs @@ -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 for Rounding { + #[inline] + fn from(radius: u8) -> Self { + Self::same(radius) + } +} + +impl From 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 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 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 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 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 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 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, + }; + } +} diff --git a/crates/epaint/src/roundingf.rs b/crates/epaint/src/roundingf.rs new file mode 100644 index 00000000..b49cbc77 --- /dev/null +++ b/crates/epaint/src/roundingf.rs @@ -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 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 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 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 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 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 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 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 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 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, + }; + } +} diff --git a/crates/epaint/src/shadow.rs b/crates/epaint/src/shadow.rs index bb386d21..5a68e45b 100644 --- a/crates/epaint/src/shadow.rs +++ b/crates/epaint/src/shadow.rs @@ -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) } diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 56af703b..1fc393c7 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -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 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 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 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 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 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 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 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 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. diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index e04eb03b..5e8b131c 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -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); }