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 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 #[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 #[deprecated = "Use `Shape::galley` or `Shape::galley_with_override_text_color` instead"]
362 pub fn galley_with_color(pos: Pos2, galley: Arc<Galley>, text_color: Color32) -> Self {
363 Self::galley_with_override_text_color(pos, galley, text_color)
364 }
365
366 #[inline]
367 pub fn mesh(mesh: impl Into<Arc<Mesh>>) -> Self {
368 let mesh = mesh.into();
369 debug_assert!(mesh.is_valid(), "Invalid mesh: {mesh:#?}");
370 Self::Mesh(mesh)
371 }
372
373 pub fn image(texture_id: TextureId, rect: Rect, uv: Rect, tint: Color32) -> Self {
380 let mut mesh = Mesh::with_texture(texture_id);
381 mesh.add_rect_with_uv(rect, uv, tint);
382 Self::mesh(mesh)
383 }
384
385 pub fn visual_bounding_rect(&self) -> Rect {
387 match self {
388 Self::Noop => Rect::NOTHING,
389 Self::Vec(shapes) => {
390 let mut rect = Rect::NOTHING;
391 for shape in shapes {
392 rect |= shape.visual_bounding_rect();
393 }
394 rect
395 }
396 Self::Circle(circle_shape) => circle_shape.visual_bounding_rect(),
397 Self::Ellipse(ellipse_shape) => ellipse_shape.visual_bounding_rect(),
398 Self::LineSegment { points, stroke } => {
399 if stroke.is_empty() {
400 Rect::NOTHING
401 } else {
402 Rect::from_two_pos(points[0], points[1]).expand(stroke.width / 2.0)
403 }
404 }
405 Self::Path(path_shape) => path_shape.visual_bounding_rect(),
406 Self::Rect(rect_shape) => rect_shape.visual_bounding_rect(),
407 Self::Text(text_shape) => text_shape.visual_bounding_rect(),
408 Self::Mesh(mesh) => mesh.calc_bounds(),
409 Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(),
410 Self::CubicBezier(bezier) => bezier.visual_bounding_rect(),
411 Self::Callback(custom) => custom.rect,
412 }
413 }
414}
415
416impl Shape {
418 #[inline(always)]
419 pub fn texture_id(&self) -> crate::TextureId {
420 if let Self::Mesh(mesh) = self {
421 mesh.texture_id
422 } else if let Self::Rect(rect_shape) = self {
423 rect_shape.fill_texture_id()
424 } else {
425 crate::TextureId::default()
426 }
427 }
428
429 #[inline(always)]
433 pub fn scale(&mut self, factor: f32) {
434 self.transform(TSTransform::from_scaling(factor));
435 }
436
437 #[inline(always)]
441 pub fn translate(&mut self, delta: Vec2) {
442 self.transform(TSTransform::from_translation(delta));
443 }
444
445 pub fn transform(&mut self, transform: TSTransform) {
450 match self {
451 Self::Noop => {}
452 Self::Vec(shapes) => {
453 for shape in shapes {
454 shape.transform(transform);
455 }
456 }
457 Self::Circle(circle_shape) => {
458 circle_shape.center = transform * circle_shape.center;
459 circle_shape.radius *= transform.scaling;
460 circle_shape.stroke.width *= transform.scaling;
461 }
462 Self::Ellipse(ellipse_shape) => {
463 ellipse_shape.center = transform * ellipse_shape.center;
464 ellipse_shape.radius *= transform.scaling;
465 ellipse_shape.stroke.width *= transform.scaling;
466 }
467 Self::LineSegment { points, stroke } => {
468 for p in points {
469 *p = transform * *p;
470 }
471 stroke.width *= transform.scaling;
472 }
473 Self::Path(path_shape) => {
474 for p in &mut path_shape.points {
475 *p = transform * *p;
476 }
477 path_shape.stroke.width *= transform.scaling;
478 }
479 Self::Rect(rect_shape) => {
480 rect_shape.rect = transform * rect_shape.rect;
481 rect_shape.corner_radius *= transform.scaling;
482 rect_shape.stroke.width *= transform.scaling;
483 rect_shape.blur_width *= transform.scaling;
484 }
485 Self::Text(text_shape) => {
486 text_shape.transform(transform);
487 }
488 Self::Mesh(mesh) => {
489 Arc::make_mut(mesh).transform(transform);
490 }
491 Self::QuadraticBezier(bezier) => {
492 for p in &mut bezier.points {
493 *p = transform * *p;
494 }
495 bezier.stroke.width *= transform.scaling;
496 }
497 Self::CubicBezier(bezier) => {
498 for p in &mut bezier.points {
499 *p = transform * *p;
500 }
501 bezier.stroke.width *= transform.scaling;
502 }
503 Self::Callback(shape) => {
504 shape.rect = transform * shape.rect;
505 }
506 }
507 }
508}
509
510fn points_from_line(
514 path: &[Pos2],
515 spacing: f32,
516 radius: f32,
517 color: Color32,
518 shapes: &mut Vec<Shape>,
519) {
520 let mut position_on_segment = 0.0;
521 for window in path.windows(2) {
522 let (start, end) = (window[0], window[1]);
523 let vector = end - start;
524 let segment_length = vector.length();
525 while position_on_segment < segment_length {
526 let new_point = start + vector * (position_on_segment / segment_length);
527 shapes.push(Shape::circle_filled(new_point, radius, color));
528 position_on_segment += spacing;
529 }
530 position_on_segment -= segment_length;
531 }
532}
533
534fn dashes_from_line(
536 path: &[Pos2],
537 stroke: Stroke,
538 dash_lengths: &[f32],
539 gap_lengths: &[f32],
540 shapes: &mut Vec<Shape>,
541 dash_offset: f32,
542) {
543 assert_eq!(
544 dash_lengths.len(),
545 gap_lengths.len(),
546 "Mismatched dash and gap lengths, got dash_lengths: {}, gap_lengths: {}",
547 dash_lengths.len(),
548 gap_lengths.len()
549 );
550 let mut position_on_segment = dash_offset;
551 let mut drawing_dash = false;
552 let mut step = 0;
553 let steps = dash_lengths.len();
554 for window in path.windows(2) {
555 let (start, end) = (window[0], window[1]);
556 let vector = end - start;
557 let segment_length = vector.length();
558
559 let mut start_point = start;
560 while position_on_segment < segment_length {
561 let new_point = start + vector * (position_on_segment / segment_length);
562 if drawing_dash {
563 shapes.push(Shape::line_segment([start_point, new_point], stroke));
565 position_on_segment += gap_lengths[step];
566 step += 1;
568 if step >= steps {
569 step = 0;
570 }
571 } else {
572 start_point = new_point;
574 position_on_segment += dash_lengths[step];
575 }
576 drawing_dash = !drawing_dash;
577 }
578
579 if drawing_dash {
581 shapes.push(Shape::line_segment([start_point, end], stroke));
582 }
583
584 position_on_segment -= segment_length;
585 }
586}