1use std::sync::Arc;
4
5use emath::{Align2, Pos2, Rangef, Rect, TSTransform, Vec2, pos2};
6
7use crate::{
8 Color32, CornerRadius, Direction, Mesh, Stroke, StrokeKind, TextureId, Vertex,
9 stroke::PathStroke,
10 text::{FontId, FontsView, 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<Self>),
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 core::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 core::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 #[inline]
306 pub fn gradient_rect(rect: Rect, direction: Direction, [from, to]: [Color32; 2]) -> Self {
307 let (left_top, right_top, left_bottom, right_bottom) = match direction {
308 Direction::TopDown => (from, from, to, to),
309 Direction::BottomUp => (to, to, from, from),
310 Direction::LeftToRight => (from, to, from, to),
311 Direction::RightToLeft => (to, from, to, from),
312 };
313
314 Self::from(Mesh {
315 indices: vec![0, 1, 2, 2, 1, 3],
316 vertices: vec![
317 Vertex::untextured(rect.left_top(), left_top),
318 Vertex::untextured(rect.right_top(), right_top),
319 Vertex::untextured(rect.left_bottom(), left_bottom),
320 Vertex::untextured(rect.right_bottom(), right_bottom),
321 ],
322 texture_id: Default::default(),
323 })
324 }
325
326 #[expect(clippy::needless_pass_by_value)]
327 pub fn text(
328 fonts: &mut FontsView<'_>,
329 pos: Pos2,
330 anchor: Align2,
331 text: impl ToString,
332 font_id: FontId,
333 color: Color32,
334 ) -> Self {
335 let galley = fonts.layout_no_wrap(text.to_string(), font_id, color);
336 let rect = anchor.anchor_size(pos, galley.size());
337 Self::galley(rect.min, galley, color)
338 }
339
340 #[inline]
344 pub fn galley(pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) -> Self {
345 TextShape::new(pos, galley, fallback_color).into()
346 }
347
348 #[inline]
350 pub fn galley_with_override_text_color(
351 pos: Pos2,
352 galley: Arc<Galley>,
353 text_color: Color32,
354 ) -> Self {
355 TextShape::new(pos, galley, text_color)
356 .with_override_text_color(text_color)
357 .into()
358 }
359
360 #[inline]
361 pub fn mesh(mesh: impl Into<Arc<Mesh>>) -> Self {
362 let mesh = mesh.into();
363 debug_assert!(mesh.is_valid(), "Invalid mesh: {mesh:#?}");
364 Self::Mesh(mesh)
365 }
366
367 pub fn image(texture_id: TextureId, rect: Rect, uv: Rect, tint: Color32) -> Self {
374 let mut mesh = Mesh::with_texture(texture_id);
375 mesh.add_rect_with_uv(rect, uv, tint);
376 Self::mesh(mesh)
377 }
378
379 pub fn visual_bounding_rect(&self) -> Rect {
381 match self {
382 Self::Noop => Rect::NOTHING,
383 Self::Vec(shapes) => {
384 let mut rect = Rect::NOTHING;
385 for shape in shapes {
386 rect |= shape.visual_bounding_rect();
387 }
388 rect
389 }
390 Self::Circle(circle_shape) => circle_shape.visual_bounding_rect(),
391 Self::Ellipse(ellipse_shape) => ellipse_shape.visual_bounding_rect(),
392 Self::LineSegment { points, stroke } => {
393 if stroke.is_empty() {
394 Rect::NOTHING
395 } else {
396 Rect::from_two_pos(points[0], points[1]).expand(stroke.width / 2.0)
397 }
398 }
399 Self::Path(path_shape) => path_shape.visual_bounding_rect(),
400 Self::Rect(rect_shape) => rect_shape.visual_bounding_rect(),
401 Self::Text(text_shape) => text_shape.visual_bounding_rect(),
402 Self::Mesh(mesh) => mesh.calc_bounds(),
403 Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(),
404 Self::CubicBezier(bezier) => bezier.visual_bounding_rect(),
405 Self::Callback(custom) => custom.rect,
406 }
407 }
408}
409
410impl Shape {
412 #[inline(always)]
413 pub fn texture_id(&self) -> crate::TextureId {
414 if let Self::Mesh(mesh) = self {
415 mesh.texture_id
416 } else if let Self::Rect(rect_shape) = self {
417 rect_shape.fill_texture_id()
418 } else {
419 crate::TextureId::default()
420 }
421 }
422
423 #[inline(always)]
427 pub fn scale(&mut self, factor: f32) {
428 self.transform(TSTransform::from_scaling(factor));
429 }
430
431 #[inline(always)]
435 pub fn translate(&mut self, delta: Vec2) {
436 self.transform(TSTransform::from_translation(delta));
437 }
438
439 pub fn transform(&mut self, transform: TSTransform) {
444 match self {
445 Self::Noop => {}
446 Self::Vec(shapes) => {
447 for shape in shapes {
448 shape.transform(transform);
449 }
450 }
451 Self::Circle(circle_shape) => {
452 circle_shape.center = transform * circle_shape.center;
453 circle_shape.radius *= transform.scaling;
454 circle_shape.stroke.width *= transform.scaling;
455 }
456 Self::Ellipse(ellipse_shape) => {
457 ellipse_shape.center = transform * ellipse_shape.center;
458 ellipse_shape.radius *= transform.scaling;
459 ellipse_shape.stroke.width *= transform.scaling;
460 }
461 Self::LineSegment { points, stroke } => {
462 for p in points {
463 *p = transform * *p;
464 }
465 stroke.width *= transform.scaling;
466 }
467 Self::Path(path_shape) => {
468 for p in &mut path_shape.points {
469 *p = transform * *p;
470 }
471 path_shape.stroke.width *= transform.scaling;
472 }
473 Self::Rect(rect_shape) => {
474 rect_shape.rect = transform * rect_shape.rect;
475 rect_shape.corner_radius *= transform.scaling;
476 rect_shape.stroke.width *= transform.scaling;
477 rect_shape.blur_width *= transform.scaling;
478 }
479 Self::Text(text_shape) => {
480 text_shape.transform(transform);
481 }
482 Self::Mesh(mesh) => {
483 Arc::make_mut(mesh).transform(transform);
484 }
485 Self::QuadraticBezier(bezier) => {
486 for p in &mut bezier.points {
487 *p = transform * *p;
488 }
489 bezier.stroke.width *= transform.scaling;
490 }
491 Self::CubicBezier(bezier) => {
492 for p in &mut bezier.points {
493 *p = transform * *p;
494 }
495 bezier.stroke.width *= transform.scaling;
496 }
497 Self::Callback(shape) => {
498 shape.rect = transform * shape.rect;
499 }
500 }
501 }
502}
503
504fn points_from_line(
508 path: &[Pos2],
509 spacing: f32,
510 radius: f32,
511 color: Color32,
512 shapes: &mut Vec<Shape>,
513) {
514 let mut position_on_segment = 0.0;
515 for &[start, end] in path.array_windows() {
516 let vector = end - start;
517 let segment_length = vector.length();
518 while position_on_segment < segment_length {
519 let new_point = start + vector * (position_on_segment / segment_length);
520 shapes.push(Shape::circle_filled(new_point, radius, color));
521 position_on_segment += spacing;
522 }
523 position_on_segment -= segment_length;
524 }
525}
526
527fn dashes_from_line(
529 path: &[Pos2],
530 stroke: Stroke,
531 dash_lengths: &[f32],
532 gap_lengths: &[f32],
533 shapes: &mut Vec<Shape>,
534 dash_offset: f32,
535) {
536 assert_eq!(
537 dash_lengths.len(),
538 gap_lengths.len(),
539 "Mismatched dash and gap lengths, got dash_lengths: {}, gap_lengths: {}",
540 dash_lengths.len(),
541 gap_lengths.len()
542 );
543 let mut position_on_segment = dash_offset;
544 let mut drawing_dash = false;
545 let mut step = 0;
546 let steps = dash_lengths.len();
547 for &[start, end] in path.array_windows() {
548 let vector = end - start;
549 let segment_length = vector.length();
550
551 let mut start_point = start;
552 while position_on_segment < segment_length {
553 let new_point = start + vector * (position_on_segment / segment_length);
554 if drawing_dash {
555 shapes.push(Shape::line_segment([start_point, new_point], stroke));
557 position_on_segment += gap_lengths[step];
558 step += 1;
560 if step >= steps {
561 step = 0;
562 }
563 } else {
564 start_point = new_point;
566 position_on_segment += dash_lengths[step];
567 }
568 drawing_dash = !drawing_dash;
569 }
570
571 if drawing_dash {
573 shapes.push(Shape::line_segment([start_point, end], stroke));
574 }
575
576 position_on_segment -= segment_length;
577 }
578}