1use std::sync::Arc;
4
5use emath::{Align2, Pos2, Rangef, Rect, TSTransform, Vec2, pos2};
6
7use crate::{
8 Color32, CornerRadius, Mesh, Stroke, StrokeKind, TextureId,
9 stroke::PathStroke,
10 text::{FontId, Fonts, Galley},
11};
12
13use super::{
14 CircleShape, CubicBezierShape, EllipseShape, PaintCallback, PathShape, QuadraticBezierShape,
15 RectShape, TextShape,
16};
17
18#[must_use = "Add a Shape to a Painter"]
26#[derive(Clone, Debug, PartialEq)]
27pub enum Shape {
28 Noop,
30
31 Vec(Vec<Shape>),
34
35 Circle(CircleShape),
37
38 Ellipse(EllipseShape),
40
41 LineSegment { points: [Pos2; 2], stroke: Stroke },
43
44 Path(PathShape),
47
48 Rect(RectShape),
50
51 Text(TextShape),
55
56 Mesh(Arc<Mesh>),
62
63 QuadraticBezier(QuadraticBezierShape),
65
66 CubicBezier(CubicBezierShape),
68
69 Callback(PaintCallback),
71}
72
73#[test]
74fn shape_size() {
75 assert_eq!(
76 std::mem::size_of::<Shape>(),
77 64,
78 "Shape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
79 );
80 assert!(
81 std::mem::size_of::<Shape>() <= 64,
82 "Shape is getting way too big!"
83 );
84}
85
86#[test]
87fn shape_impl_send_sync() {
88 fn assert_send_sync<T: Send + Sync>() {}
89 assert_send_sync::<Shape>();
90}
91
92impl From<Vec<Self>> for Shape {
93 #[inline(always)]
94 fn from(shapes: Vec<Self>) -> Self {
95 Self::Vec(shapes)
96 }
97}
98
99impl From<Mesh> for Shape {
100 #[inline(always)]
101 fn from(mesh: Mesh) -> Self {
102 Self::Mesh(mesh.into())
103 }
104}
105
106impl From<Arc<Mesh>> for Shape {
107 #[inline(always)]
108 fn from(mesh: Arc<Mesh>) -> Self {
109 Self::Mesh(mesh)
110 }
111}
112
113impl Shape {
115 #[inline]
118 pub fn line_segment(points: [Pos2; 2], stroke: impl Into<Stroke>) -> Self {
119 Self::LineSegment {
120 points,
121 stroke: stroke.into(),
122 }
123 }
124
125 pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> Self {
127 let x = x.into();
128 Self::LineSegment {
129 points: [pos2(x.min, y), pos2(x.max, y)],
130 stroke: stroke.into(),
131 }
132 }
133
134 pub fn vline(x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> Self {
136 let y = y.into();
137 Self::LineSegment {
138 points: [pos2(x, y.min), pos2(x, y.max)],
139 stroke: stroke.into(),
140 }
141 }
142
143 #[inline]
147 pub fn line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
148 Self::Path(PathShape::line(points, stroke))
149 }
150
151 #[inline]
153 pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
154 Self::Path(PathShape::closed_line(points, stroke))
155 }
156
157 pub fn dotted_line(
159 path: &[Pos2],
160 color: impl Into<Color32>,
161 spacing: f32,
162 radius: f32,
163 ) -> Vec<Self> {
164 let mut shapes = Vec::new();
165 points_from_line(path, spacing, radius, color.into(), &mut shapes);
166 shapes
167 }
168
169 pub fn dashed_line(
171 path: &[Pos2],
172 stroke: impl Into<Stroke>,
173 dash_length: f32,
174 gap_length: f32,
175 ) -> Vec<Self> {
176 let mut shapes = Vec::new();
177 dashes_from_line(
178 path,
179 stroke.into(),
180 &[dash_length],
181 &[gap_length],
182 &mut shapes,
183 0.,
184 );
185 shapes
186 }
187
188 pub fn dashed_line_with_offset(
190 path: &[Pos2],
191 stroke: impl Into<Stroke>,
192 dash_lengths: &[f32],
193 gap_lengths: &[f32],
194 dash_offset: f32,
195 ) -> Vec<Self> {
196 let mut shapes = Vec::new();
197 dashes_from_line(
198 path,
199 stroke.into(),
200 dash_lengths,
201 gap_lengths,
202 &mut shapes,
203 dash_offset,
204 );
205 shapes
206 }
207
208 pub fn dashed_line_many(
211 points: &[Pos2],
212 stroke: impl Into<Stroke>,
213 dash_length: f32,
214 gap_length: f32,
215 shapes: &mut Vec<Self>,
216 ) {
217 dashes_from_line(
218 points,
219 stroke.into(),
220 &[dash_length],
221 &[gap_length],
222 shapes,
223 0.,
224 );
225 }
226
227 pub fn dashed_line_many_with_offset(
230 points: &[Pos2],
231 stroke: impl Into<Stroke>,
232 dash_lengths: &[f32],
233 gap_lengths: &[f32],
234 dash_offset: f32,
235 shapes: &mut Vec<Self>,
236 ) {
237 dashes_from_line(
238 points,
239 stroke.into(),
240 dash_lengths,
241 gap_lengths,
242 shapes,
243 dash_offset,
244 );
245 }
246
247 #[inline]
251 pub fn convex_polygon(
252 points: Vec<Pos2>,
253 fill: impl Into<Color32>,
254 stroke: impl Into<PathStroke>,
255 ) -> Self {
256 Self::Path(PathShape::convex_polygon(points, fill, stroke))
257 }
258
259 #[inline]
260 pub fn circle_filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
261 Self::Circle(CircleShape::filled(center, radius, fill_color))
262 }
263
264 #[inline]
265 pub fn circle_stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
266 Self::Circle(CircleShape::stroke(center, radius, stroke))
267 }
268
269 #[inline]
270 pub fn ellipse_filled(center: Pos2, radius: Vec2, fill_color: impl Into<Color32>) -> Self {
271 Self::Ellipse(EllipseShape::filled(center, radius, fill_color))
272 }
273
274 #[inline]
275 pub fn ellipse_stroke(center: Pos2, radius: Vec2, stroke: impl Into<Stroke>) -> Self {
276 Self::Ellipse(EllipseShape::stroke(center, radius, stroke))
277 }
278
279 #[inline]
281 pub fn rect_filled(
282 rect: Rect,
283 corner_radius: impl Into<CornerRadius>,
284 fill_color: impl Into<Color32>,
285 ) -> Self {
286 Self::Rect(RectShape::filled(rect, corner_radius, fill_color))
287 }
288
289 #[inline]
291 pub fn rect_stroke(
292 rect: Rect,
293 corner_radius: impl Into<CornerRadius>,
294 stroke: impl Into<Stroke>,
295 stroke_kind: StrokeKind,
296 ) -> Self {
297 Self::Rect(RectShape::stroke(rect, corner_radius, stroke, stroke_kind))
298 }
299
300 #[expect(clippy::needless_pass_by_value)]
301 pub fn text(
302 fonts: &Fonts,
303 pos: Pos2,
304 anchor: Align2,
305 text: impl ToString,
306 font_id: FontId,
307 color: Color32,
308 ) -> Self {
309 let galley = fonts.layout_no_wrap(text.to_string(), font_id, color);
310 let rect = anchor.anchor_size(pos, galley.size());
311 Self::galley(rect.min, galley, color)
312 }
313
314 #[inline]
318 pub fn galley(pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) -> Self {
319 TextShape::new(pos, galley, fallback_color).into()
320 }
321
322 #[inline]
324 pub fn galley_with_override_text_color(
325 pos: Pos2,
326 galley: Arc<Galley>,
327 text_color: Color32,
328 ) -> Self {
329 TextShape::new(pos, galley, text_color)
330 .with_override_text_color(text_color)
331 .into()
332 }
333
334 #[inline]
335 #[deprecated = "Use `Shape::galley` or `Shape::galley_with_override_text_color` instead"]
336 pub fn galley_with_color(pos: Pos2, galley: Arc<Galley>, text_color: Color32) -> Self {
337 Self::galley_with_override_text_color(pos, galley, text_color)
338 }
339
340 #[inline]
341 pub fn mesh(mesh: impl Into<Arc<Mesh>>) -> Self {
342 let mesh = mesh.into();
343 debug_assert!(mesh.is_valid(), "Invalid mesh: {mesh:#?}");
344 Self::Mesh(mesh)
345 }
346
347 pub fn image(texture_id: TextureId, rect: Rect, uv: Rect, tint: Color32) -> Self {
354 let mut mesh = Mesh::with_texture(texture_id);
355 mesh.add_rect_with_uv(rect, uv, tint);
356 Self::mesh(mesh)
357 }
358
359 pub fn visual_bounding_rect(&self) -> Rect {
361 match self {
362 Self::Noop => Rect::NOTHING,
363 Self::Vec(shapes) => {
364 let mut rect = Rect::NOTHING;
365 for shape in shapes {
366 rect |= shape.visual_bounding_rect();
367 }
368 rect
369 }
370 Self::Circle(circle_shape) => circle_shape.visual_bounding_rect(),
371 Self::Ellipse(ellipse_shape) => ellipse_shape.visual_bounding_rect(),
372 Self::LineSegment { points, stroke } => {
373 if stroke.is_empty() {
374 Rect::NOTHING
375 } else {
376 Rect::from_two_pos(points[0], points[1]).expand(stroke.width / 2.0)
377 }
378 }
379 Self::Path(path_shape) => path_shape.visual_bounding_rect(),
380 Self::Rect(rect_shape) => rect_shape.visual_bounding_rect(),
381 Self::Text(text_shape) => text_shape.visual_bounding_rect(),
382 Self::Mesh(mesh) => mesh.calc_bounds(),
383 Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(),
384 Self::CubicBezier(bezier) => bezier.visual_bounding_rect(),
385 Self::Callback(custom) => custom.rect,
386 }
387 }
388}
389
390impl Shape {
392 #[inline(always)]
393 pub fn texture_id(&self) -> crate::TextureId {
394 if let Self::Mesh(mesh) = self {
395 mesh.texture_id
396 } else if let Self::Rect(rect_shape) = self {
397 rect_shape.fill_texture_id()
398 } else {
399 crate::TextureId::default()
400 }
401 }
402
403 #[inline(always)]
407 pub fn scale(&mut self, factor: f32) {
408 self.transform(TSTransform::from_scaling(factor));
409 }
410
411 #[inline(always)]
415 pub fn translate(&mut self, delta: Vec2) {
416 self.transform(TSTransform::from_translation(delta));
417 }
418
419 pub fn transform(&mut self, transform: TSTransform) {
424 match self {
425 Self::Noop => {}
426 Self::Vec(shapes) => {
427 for shape in shapes {
428 shape.transform(transform);
429 }
430 }
431 Self::Circle(circle_shape) => {
432 circle_shape.center = transform * circle_shape.center;
433 circle_shape.radius *= transform.scaling;
434 circle_shape.stroke.width *= transform.scaling;
435 }
436 Self::Ellipse(ellipse_shape) => {
437 ellipse_shape.center = transform * ellipse_shape.center;
438 ellipse_shape.radius *= transform.scaling;
439 ellipse_shape.stroke.width *= transform.scaling;
440 }
441 Self::LineSegment { points, stroke } => {
442 for p in points {
443 *p = transform * *p;
444 }
445 stroke.width *= transform.scaling;
446 }
447 Self::Path(path_shape) => {
448 for p in &mut path_shape.points {
449 *p = transform * *p;
450 }
451 path_shape.stroke.width *= transform.scaling;
452 }
453 Self::Rect(rect_shape) => {
454 rect_shape.rect = transform * rect_shape.rect;
455 rect_shape.corner_radius *= transform.scaling;
456 rect_shape.stroke.width *= transform.scaling;
457 rect_shape.blur_width *= transform.scaling;
458 }
459 Self::Text(text_shape) => {
460 text_shape.transform(transform);
461 }
462 Self::Mesh(mesh) => {
463 Arc::make_mut(mesh).transform(transform);
464 }
465 Self::QuadraticBezier(bezier) => {
466 for p in &mut bezier.points {
467 *p = transform * *p;
468 }
469 bezier.stroke.width *= transform.scaling;
470 }
471 Self::CubicBezier(bezier) => {
472 for p in &mut bezier.points {
473 *p = transform * *p;
474 }
475 bezier.stroke.width *= transform.scaling;
476 }
477 Self::Callback(shape) => {
478 shape.rect = transform * shape.rect;
479 }
480 }
481 }
482}
483
484fn points_from_line(
488 path: &[Pos2],
489 spacing: f32,
490 radius: f32,
491 color: Color32,
492 shapes: &mut Vec<Shape>,
493) {
494 let mut position_on_segment = 0.0;
495 for window in path.windows(2) {
496 let (start, end) = (window[0], window[1]);
497 let vector = end - start;
498 let segment_length = vector.length();
499 while position_on_segment < segment_length {
500 let new_point = start + vector * (position_on_segment / segment_length);
501 shapes.push(Shape::circle_filled(new_point, radius, color));
502 position_on_segment += spacing;
503 }
504 position_on_segment -= segment_length;
505 }
506}
507
508fn dashes_from_line(
510 path: &[Pos2],
511 stroke: Stroke,
512 dash_lengths: &[f32],
513 gap_lengths: &[f32],
514 shapes: &mut Vec<Shape>,
515 dash_offset: f32,
516) {
517 assert_eq!(
518 dash_lengths.len(),
519 gap_lengths.len(),
520 "Mismatched dash and gap lengths, got dash_lengths: {}, gap_lengths: {}",
521 dash_lengths.len(),
522 gap_lengths.len()
523 );
524 let mut position_on_segment = dash_offset;
525 let mut drawing_dash = false;
526 let mut step = 0;
527 let steps = dash_lengths.len();
528 for window in path.windows(2) {
529 let (start, end) = (window[0], window[1]);
530 let vector = end - start;
531 let segment_length = vector.length();
532
533 let mut start_point = start;
534 while position_on_segment < segment_length {
535 let new_point = start + vector * (position_on_segment / segment_length);
536 if drawing_dash {
537 shapes.push(Shape::line_segment([start_point, new_point], stroke));
539 position_on_segment += gap_lengths[step];
540 step += 1;
542 if step >= steps {
543 step = 0;
544 }
545 } else {
546 start_point = new_point;
548 position_on_segment += dash_lengths[step];
549 }
550 drawing_dash = !drawing_dash;
551 }
552
553 if drawing_dash {
555 shapes.push(Shape::line_segment([start_point, end], stroke));
556 }
557
558 position_on_segment -= segment_length;
559 }
560}