1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 05:40:03 -04:00

Per-corner rounding of rectangles (#1206)

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
4JX
2022-02-05 17:48:55 +01:00
committed by GitHub
parent 0fa4bb9c64
commit 7e7b9e1919
14 changed files with 197 additions and 78 deletions

View File

@@ -110,7 +110,7 @@ pub use {
image::{AlphaImage, ColorImage, ImageData, ImageDelta},
mesh::{Mesh, Mesh16, Vertex},
shadow::Shadow,
shape::{CircleShape, PathShape, RectShape, Shape, TextShape},
shape::{CircleShape, PathShape, RectShape, Rounding, Shape, TextShape},
stats::PaintStats,
stroke::Stroke,
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},

View File

@@ -46,17 +46,23 @@ impl Shadow {
}
}
pub fn tessellate(&self, rect: emath::Rect, corner_radius: f32) -> Mesh {
pub fn tessellate(&self, rect: emath::Rect, corner_radius: impl Into<Rounding>) -> Mesh {
// tessellator.clip_rect = clip_rect; // TODO: culling
let Self { extrusion, color } = *self;
let cr: Rounding = corner_radius.into();
let half_ext = 0.5 * extrusion;
let ext_corner_radius = Rounding {
nw: cr.nw + half_ext,
ne: cr.ne + half_ext,
sw: cr.sw + half_ext,
se: cr.se + half_ext,
};
use crate::tessellator::*;
let rect = RectShape::filled(
rect.expand(0.5 * extrusion),
corner_radius + 0.5 * extrusion,
color,
);
let rect = RectShape::filled(rect.expand(half_ext), ext_corner_radius, color);
let mut tessellator = Tessellator::from_options(TessellationOptions {
aa_size: extrusion,
anti_alias: true,

View File

@@ -114,12 +114,20 @@ impl Shape {
}
#[inline]
pub fn rect_filled(rect: Rect, corner_radius: f32, fill_color: impl Into<Color32>) -> Self {
pub fn rect_filled(
rect: Rect,
corner_radius: impl Into<Rounding>,
fill_color: impl Into<Color32>,
) -> Self {
Self::Rect(RectShape::filled(rect, corner_radius, fill_color))
}
#[inline]
pub fn rect_stroke(rect: Rect, corner_radius: f32, stroke: impl Into<Stroke>) -> Self {
pub fn rect_stroke(
rect: Rect,
corner_radius: impl Into<Rounding>,
stroke: impl Into<Stroke>,
) -> Self {
Self::Rect(RectShape::stroke(rect, corner_radius, stroke))
}
@@ -321,28 +329,36 @@ impl From<PathShape> for Shape {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RectShape {
pub rect: Rect,
/// How rounded the corners are. Use `0.0` for no rounding.
pub corner_radius: f32,
/// How rounded the corners are. Use `Rounding::none()` for no rounding.
pub corner_radius: Rounding,
pub fill: Color32,
pub stroke: Stroke,
}
impl RectShape {
#[inline]
pub fn filled(rect: Rect, corner_radius: f32, fill_color: impl Into<Color32>) -> Self {
pub fn filled(
rect: Rect,
corner_radius: impl Into<Rounding>,
fill_color: impl Into<Color32>,
) -> Self {
Self {
rect,
corner_radius,
corner_radius: corner_radius.into(),
fill: fill_color.into(),
stroke: Default::default(),
}
}
#[inline]
pub fn stroke(rect: Rect, corner_radius: f32, stroke: impl Into<Stroke>) -> Self {
pub fn stroke(
rect: Rect,
corner_radius: impl Into<Rounding>,
stroke: impl Into<Stroke>,
) -> Self {
Self {
rect,
corner_radius,
corner_radius: corner_radius.into(),
fill: Default::default(),
stroke: stroke.into(),
}
@@ -362,6 +378,57 @@ 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 {
pub nw: f32,
pub ne: f32,
pub sw: f32,
pub se: f32,
}
impl Default for Rounding {
#[inline]
fn default() -> Self {
Self::none()
}
}
impl From<f32> for Rounding {
#[inline]
fn from(radius: f32) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
}
impl Rounding {
#[inline]
pub fn same(radius: f32) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
#[inline]
pub fn none() -> Self {
Self {
nw: 0.0,
ne: 0.0,
sw: 0.0,
se: 0.0,
}
}
}
// ----------------------------------------------------------------------------
/// How to paint some text on screen.

View File

@@ -179,20 +179,20 @@ impl Path {
pub mod path {
//! Helpers for constructing paths
use crate::shape::Rounding;
use super::*;
/// overwrites existing points
pub fn rounded_rectangle(path: &mut Vec<Pos2>, rect: Rect, corner_radius: f32) {
pub fn rounded_rectangle(path: &mut Vec<Pos2>, rect: Rect, corner_radius: Rounding) {
path.clear();
let min = rect.min;
let max = rect.max;
let cr = corner_radius
.min(rect.width() * 0.5)
.min(rect.height() * 0.5);
let cr = clamp_radius(corner_radius, rect);
if cr <= 0.0 {
if cr == Rounding::none() {
let min = rect.min;
let max = rect.max;
path.reserve(4);
@@ -201,10 +201,10 @@ pub mod path {
path.push(pos2(max.x, max.y));
path.push(pos2(min.x, max.y));
} else {
add_circle_quadrant(path, pos2(max.x - cr, max.y - cr), cr, 0.0);
add_circle_quadrant(path, pos2(min.x + cr, max.y - cr), cr, 1.0);
add_circle_quadrant(path, pos2(min.x + cr, min.y + cr), cr, 2.0);
add_circle_quadrant(path, pos2(max.x - cr, min.y + cr), cr, 3.0);
add_circle_quadrant(path, pos2(max.x - cr.se, max.y - cr.se), cr.se, 0.0);
add_circle_quadrant(path, pos2(min.x + cr.sw, max.y - cr.sw), cr.sw, 1.0);
add_circle_quadrant(path, pos2(min.x + cr.nw, min.y + cr.nw), cr.nw, 2.0);
add_circle_quadrant(path, pos2(max.x - cr.ne, min.y + cr.ne), cr.ne, 3.0);
}
}
@@ -242,6 +242,20 @@ pub mod path {
path.push(center + radius * Vec2::angled(angle));
}
}
// Ensures the radius of each corner is within a valid range
fn clamp_radius(corner_radius: Rounding, rect: Rect) -> Rounding {
let half_width = rect.width() * 0.5;
let half_height = rect.height() * 0.5;
let max_cr = half_width.min(half_height);
Rounding {
nw: corner_radius.nw.at_most(max_cr),
ne: corner_radius.ne.at_most(max_cr),
sw: corner_radius.sw.at_most(max_cr),
se: corner_radius.se.at_most(max_cr),
}
}
}
// ----------------------------------------------------------------------------