mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 07:03: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:
@@ -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},
|
||||
|
||||
@@ -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: _,
|
||||
|
||||
107
crates/epaint/src/shapes/arc_pie_shape.rs
Normal file
107
crates/epaint/src/shapes/arc_pie_shape.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -203,6 +203,7 @@ impl PaintStats {
|
||||
}
|
||||
}
|
||||
Shape::Noop
|
||||
| Shape::ArcPie { .. }
|
||||
| Shape::Circle { .. }
|
||||
| Shape::Ellipse { .. }
|
||||
| Shape::LineSegment { .. }
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
use emath::{pos2, remap, vec2, GuiRounding as _, NumExt as _, 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.
|
||||
@@ -2288,6 +2412,7 @@ impl Tessellator {
|
||||
|
||||
Shape::Noop
|
||||
| Shape::Text(_)
|
||||
| Shape::ArcPie(_)
|
||||
| Shape::Circle(_)
|
||||
| Shape::Mesh(_)
|
||||
| Shape::LineSegment { .. }
|
||||
|
||||
Reference in New Issue
Block a user