1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00

epaint: Introduce ArcPieShape for Simplified Arc and Pie Rendering

Enables straightforward painting of arcs and pies using specified start and end angles.
This commit is contained in:
Varphone Wong
2024-07-17 16:41:26 +08:00
parent 9cdcf78c04
commit aa9b082b52
7 changed files with 353 additions and 10 deletions

View File

@@ -56,8 +56,8 @@ pub use self::{
mesh::{Mesh, Mesh16, Vertex},
shadow::Shadow,
shapes::{
CircleShape, CubicBezierShape, EllipseShape, PaintCallback, PaintCallbackInfo, PathShape,
QuadraticBezierShape, RectShape, Shape, TextShape,
ArcPieShape, CircleShape, CubicBezierShape, EllipseShape, PaintCallback, PaintCallbackInfo,
PathShape, QuadraticBezierShape, RectShape, Shape, TextShape,
},
stats::PaintStats,
stroke::{PathStroke, Stroke, StrokeKind},

View File

@@ -1,8 +1,8 @@
use std::sync::Arc;
use crate::{
color, CircleShape, Color32, ColorMode, CubicBezierShape, EllipseShape, Mesh, PathShape,
QuadraticBezierShape, RectShape, Shape, TextShape,
color, ArcPieShape, CircleShape, Color32, ColorMode, CubicBezierShape, EllipseShape, Mesh,
PathShape, QuadraticBezierShape, RectShape, Shape, TextShape,
};
/// Remember to handle [`Color32::PLACEHOLDER`] specially!
@@ -46,6 +46,31 @@ pub fn adjust_colors(
adjust_color_mode(&mut stroke.color, adjust_color);
}
Shape::ArcPie(ArcPieShape {
center: _,
radius: _,
start_angle: _,
end_angle: _,
closed,
fill,
stroke,
}) => {
if *closed {
adjust_color(fill);
}
match &stroke.color {
color::ColorMode::Solid(mut col) => adjust_color(&mut col),
color::ColorMode::UV(callback) => {
let callback = callback.clone();
stroke.color = color::ColorMode::UV(Arc::new(Box::new(move |rect, pos| {
let mut col = callback(rect, pos);
adjust_color(&mut col);
col
})));
}
}
}
Shape::Circle(CircleShape {
center: _,
radius: _,

View File

@@ -0,0 +1,107 @@
use crate::*;
/// A arc or pie slice with a given start and end angle.
#[derive(Clone, Debug, PartialEq)]
pub struct ArcPieShape {
pub center: Pos2,
pub radius: f32,
pub start_angle: f32,
pub end_angle: f32,
pub closed: bool,
pub fill: Color32,
pub stroke: PathStroke,
}
impl ArcPieShape {
/// Create a new arc or pie shape.
///
/// # Arguments
///
/// * `center` - The center of the arc or pie.
/// * `radius` - The radius of the arc or pie.
/// * `start_angle` - The start angle of the arc or pie, in radians.
/// * `end_angle` - The end angle of the arc or pie, in radians.
/// * `closed` - If true, connect the center with the start and end points.
/// * `fill` - The fill color of the arc or pie.
/// * `stroke` - The stroke of the arc or pie.
pub fn new(
center: Pos2,
radius: f32,
start_angle: f32,
end_angle: f32,
closed: bool,
fill: impl Into<Color32>,
stroke: impl Into<PathStroke>,
) -> Self {
Self {
center,
radius,
start_angle,
end_angle,
closed,
fill: fill.into(),
stroke: stroke.into(),
}
}
/// Create a new arc shape.
///
/// # Arguments
///
/// * `center` - The center of the arc.
/// * `radius` - The radius of the arc.
/// * `start_angle` - The start angle of the arc, in radians.
/// * `end_angle` - The end angle of the arc, in radians.
/// * `stroke` - The stroke of the arc.
pub fn arc(
center: Pos2,
radius: f32,
start_angle: f32,
end_angle: f32,
stroke: impl Into<PathStroke>,
) -> Self {
Self::new(
center,
radius,
start_angle,
end_angle,
false,
Color32::TRANSPARENT,
stroke,
)
}
/// Create a new pie shape.
///
/// # Arguments
///
/// * `center` - The center of the pie.
/// * `radius` - The radius of the pie.
/// * `start_angle` - The start angle of the pie, in radians.
/// * `end_angle` - The end angle of the pie, in radians.
/// * `fill` - The fill color of the pie.
/// * `stroke` - The stroke of the pie.
pub fn pie(
center: Pos2,
radius: f32,
start_angle: f32,
end_angle: f32,
fill: impl Into<Color32>,
stroke: impl Into<PathStroke>,
) -> Self {
Self::new(center, radius, start_angle, end_angle, true, fill, stroke)
}
/// The visual bounding rectangle (includes stroke width)
pub fn visual_bounding_rect(&self) -> Rect {
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
Rect::NOTHING
} else {
let rect = Rect::from_center_size(self.center, vec2(self.radius, self.radius));
let start =
self.center + vec2(self.start_angle.cos(), self.start_angle.sin()) * self.radius;
let end = self.center + vec2(self.end_angle.cos(), self.end_angle.sin()) * self.radius;
rect.union(Rect::from_two_pos(start, end))
}
}
}

View File

@@ -1,3 +1,4 @@
mod arc_pie_shape;
mod bezier_shape;
mod circle_shape;
mod ellipse_shape;
@@ -8,6 +9,7 @@ mod shape;
mod text_shape;
pub use self::{
arc_pie_shape::ArcPieShape,
bezier_shape::{CubicBezierShape, QuadraticBezierShape},
circle_shape::CircleShape,
ellipse_shape::EllipseShape,

View File

@@ -11,8 +11,8 @@ use crate::{
};
use super::{
CircleShape, CubicBezierShape, EllipseShape, PaintCallback, PathShape, QuadraticBezierShape,
RectShape, TextShape,
ArcPieShape, CircleShape, CubicBezierShape, EllipseShape, PaintCallback, PathShape,
QuadraticBezierShape, RectShape, TextShape,
};
/// A paint primitive such as a circle or a piece of text.
@@ -32,6 +32,9 @@ pub enum Shape {
/// For performance reasons it is better to avoid it.
Vec(Vec<Shape>),
/// An arc or pie with a given start and end angle.
ArcPie(ArcPieShape),
/// Circle with optional outline and fill.
Circle(CircleShape),
@@ -255,6 +258,80 @@ impl Shape {
Self::Path(PathShape::convex_polygon(points, fill, stroke))
}
/// Generates an arc with a given start and end angle.
///
/// This function creates an arc centered at a specified point, with a specified radius.
/// The arc starts at the `start_angle` and ends at the `end_angle`.
/// Angles are specified in radians, with positive angles indicating clockwise rotation and negative angles indicating counterclockwise rotation.
///
/// # Arguments
///
/// * `center` - The center point of the arc.
/// * `radius` - The radius of the arc.
/// * `start_angle` - The start angle of the arc, in radians.
/// * `end_angle` - The end angle of the arc, in radians.
/// * `stroke` - The stroke of the arc.
///
/// # Example
///
/// ```no_run
/// # use epaint::{pos2, Color32, Shape, Stroke};
/// let arc = Shape::arc(pos2(100.0, 100.0), 50.0, 0.0, std::f32::consts::PI, Stroke::new(3.0, Color32::RED));
/// ```
pub fn arc(
center: Pos2,
radius: f32,
start_angle: f32,
end_angle: f32,
stroke: impl Into<PathStroke>,
) -> Self {
Self::ArcPie(ArcPieShape::arc(
center,
radius,
start_angle,
end_angle,
stroke,
))
}
/// Generates an pie with a given start and end angle.
///
/// This function creates an arc centered at a specified point, with a specified radius.
/// The pie starts at the `start_angle` and ends at the `end_angle`.
/// Angles are specified in radians, with positive angles indicating clockwise rotation and negative angles indicating counterclockwise rotation.
///
/// # Arguments
///
/// * `center` - The center point of the pie.
/// * `radius` - The radius of the pie.
/// * `start_angle` - The start angle of the pie, in radians.
/// * `end_angle` - The end angle of the pie, in radians.
/// * `stroke` - The stroke of the pie.
///
/// # Example
///
/// ```no_run
/// # use epaint::{pos2, Color32, Shape, Stroke};
/// let pie = Shape::pie(pos2(100.0, 100.0), 50.0, 0.0, std::f32::consts::PI, Color32::BLUE, Stroke::new(3.0, Color32::RED));
/// ```
pub fn pie(
center: Pos2,
radius: f32,
start_angle: f32,
end_angle: f32,
fill: impl Into<Color32>,
stroke: impl Into<PathStroke>,
) -> Self {
Self::ArcPie(ArcPieShape::pie(
center,
radius,
start_angle,
end_angle,
fill,
stroke,
))
}
#[inline]
pub fn circle_filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
Self::Circle(CircleShape::filled(center, radius, fill_color))
@@ -366,6 +443,7 @@ impl Shape {
}
rect
}
Self::ArcPie(arc_pie_shape) => arc_pie_shape.visual_bounding_rect(),
Self::Circle(circle_shape) => circle_shape.visual_bounding_rect(),
Self::Ellipse(ellipse_shape) => ellipse_shape.visual_bounding_rect(),
Self::LineSegment { points, stroke } => {
@@ -427,6 +505,11 @@ impl Shape {
shape.transform(transform);
}
}
Self::ArcPie(arc_pie_shape) => {
arc_pie_shape.center = transform * arc_pie_shape.center;
arc_pie_shape.radius *= transform.scaling;
arc_pie_shape.stroke.width *= transform.scaling;
}
Self::Circle(circle_shape) => {
circle_shape.center = transform * circle_shape.center;
circle_shape.radius *= transform.scaling;

View File

@@ -200,6 +200,7 @@ impl PaintStats {
}
}
Shape::Noop
| Shape::ArcPie { .. }
| Shape::Circle { .. }
| Shape::Ellipse { .. }
| Shape::LineSegment { .. }

View File

@@ -8,10 +8,10 @@
use emath::{pos2, remap, vec2, GuiRounding as _, NumExt, Pos2, Rect, Rot2, Vec2};
use crate::{
color::ColorMode, emath, stroke::PathStroke, texture_atlas::PreparedDisc, CircleShape,
ClippedPrimitive, ClippedShape, Color32, CornerRadiusF32, CubicBezierShape, EllipseShape, Mesh,
PathShape, Primitive, QuadraticBezierShape, RectShape, Shape, Stroke, StrokeKind, TextShape,
TextureId, Vertex, WHITE_UV,
color::ColorMode, emath, stroke::PathStroke, texture_atlas::PreparedDisc, ArcPieShape,
CircleShape, ClippedPrimitive, ClippedShape, Color32, CornerRadiusF32, CubicBezierShape,
EllipseShape, Mesh, PathShape, Primitive, QuadraticBezierShape, RectShape, Shape, Stroke,
StrokeKind, TextShape, TextureId, Vertex, WHITE_UV,
};
// ----------------------------------------------------------------------------
@@ -475,6 +475,59 @@ impl Path {
}
}
fn add_arc_pie(
&mut self,
center: Pos2,
radius: f32,
start_angle: f32,
end_angle: f32,
closed: bool,
) {
use std::f32::consts::TAU;
let num_segs = if radius <= 2.0 {
8
} else if radius <= 5.0 {
16
} else if radius < 18.0 {
32
} else if radius < 50.0 {
64
} else {
128
};
let angle = (end_angle - start_angle).clamp(-TAU + f32::EPSILON, TAU - f32::EPSILON);
let mut points = Vec::with_capacity(num_segs + 3);
let step = angle / num_segs as f32;
if closed {
points.push(center);
}
for i in 0..=num_segs {
let a = start_angle + step * i as f32;
points.push(pos2(
center.x + radius * a.cos(),
center.y + radius * a.sin(),
));
}
if closed {
points.push(center);
self.add_line_loop(&points[..]);
} else {
self.add_open_points(&points[..]);
}
}
/// Add an arc line.
pub fn add_arc(&mut self, center: Pos2, radius: f32, start_angle: f32, end_angle: f32) {
self.add_arc_pie(center, radius, start_angle, end_angle, false);
}
/// Add a pie slice.
pub fn add_pie(&mut self, center: Pos2, radius: f32, start_angle: f32, end_angle: f32) {
self.add_arc_pie(center, radius, start_angle, end_angle, true);
}
/// The path is taken to be closed (i.e. returning to the start again).
///
/// Calling this may reverse the vertices in the path if they are wrong winding order.
@@ -1429,6 +1482,9 @@ impl Tessellator {
self.tessellate_shape(shape, out);
}
}
Shape::ArcPie(arc_pie_shape) => {
self.tessellate_arc_pie(arc_pie_shape, out);
}
Shape::Circle(circle) => {
self.tessellate_circle(circle, out);
}
@@ -1481,6 +1537,74 @@ impl Tessellator {
}
}
/// Tessellate a single [`ArcPieShape`] into a [`Mesh`].
///
/// * `arc_pie_shape`: the arc or pie to tessellate.
/// * `out`: triangles are appended to this.
pub fn tessellate_arc_pie(&mut self, arc_pie_shape: ArcPieShape, out: &mut Mesh) {
let ArcPieShape {
center,
radius,
start_angle,
end_angle,
closed,
fill,
stroke,
} = arc_pie_shape;
if radius <= 0.0
|| start_angle == end_angle
|| stroke.width <= 0.0 && (!closed || fill == Color32::TRANSPARENT)
{
return;
}
if self.options.coarse_tessellation_culling
&& !self
.clip_rect
.expand(radius + stroke.width)
.contains(center)
{
return;
}
// If the arc is a full circle, we can just use the circle function.
if (end_angle - start_angle).abs() >= std::f32::consts::TAU {
let stroke_color = match stroke.color {
ColorMode::Solid(color) => color,
ColorMode::UV(callback) => {
// TODO: Currently, CircleShape does not support PathStroke.
// As a workaround, the stroke color is set to the center color.
// This needs to be revisited once CircleShape gains PathStroke support.
callback(Rect::from_center_size(center, Vec2::splat(radius)), center)
}
};
let stroke = Stroke::new(stroke.width, stroke_color);
let circle = CircleShape {
center,
radius,
fill,
stroke,
};
return self.tessellate_circle(circle, out);
}
self.scratchpad_path.clear();
if closed {
self.scratchpad_path
.add_pie(center, radius, start_angle, end_angle);
self.scratchpad_path.fill(self.feathering, fill, out);
self.scratchpad_path
.stroke_closed(self.feathering, &stroke, out);
} else {
self.scratchpad_path
.add_arc(center, radius, start_angle, end_angle);
self.scratchpad_path
.stroke_open(self.feathering, &stroke, out);
}
}
/// Tessellate a single [`CircleShape`] into a [`Mesh`].
///
/// * `shape`: the circle to tessellate.
@@ -2336,6 +2460,7 @@ impl Tessellator {
Shape::Noop
| Shape::Text(_)
| Shape::ArcPie(_)
| Shape::Circle(_)
| Shape::Mesh(_)
| Shape::LineSegment { .. }