1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 15:13:12 -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 dbe8176408
commit 3f9cb13499
5 changed files with 344 additions and 2 deletions

View File

@@ -49,8 +49,8 @@ pub use self::{
mesh::{Mesh, Mesh16, Vertex},
shadow::Shadow,
shape::{
CircleShape, EllipseShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape,
Rounding, Shape, TextShape,
ArcPieShape, CircleShape, EllipseShape, PaintCallback, PaintCallbackInfo, PathShape,
RectShape, Rounding, Shape, TextShape,
},
stats::PaintStats,
stroke::{PathStroke, Stroke},

View File

@@ -28,6 +28,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),
@@ -233,6 +236,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))
@@ -340,6 +417,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 } => {
@@ -428,6 +506,11 @@ impl Shape {
rect_shape.stroke.width *= transform.scaling;
rect_shape.rounding *= transform.scaling;
}
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::Text(text_shape) => {
text_shape.pos = transform * text_shape.pos;
@@ -467,6 +550,114 @@ impl Shape {
// ----------------------------------------------------------------------------
/// 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))
}
}
}
// ----------------------------------------------------------------------------
/// How to paint a circle.
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]

View File

@@ -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

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

View File

@@ -477,6 +477,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);
}
/// Open-ended.
pub fn stroke_open(&mut self, feathering: f32, stroke: &PathStroke, out: &mut Mesh) {
stroke_path(feathering, &mut self.0, PathType::Open, stroke, out);
@@ -1356,6 +1409,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);
}
@@ -1406,6 +1462,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.
@@ -2041,6 +2165,7 @@ impl Tessellator {
Shape::Noop
| Shape::Text(_)
| Shape::ArcPie(_)
| Shape::Circle(_)
| Shape::Mesh(_)
| Shape::LineSegment { .. }