1use std::sync::Arc;
2
3use sequoia_core::{Color, ImageSource, Point, Rect, Shadow, Vec2};
4use sequoia_draw::{
5 Dash, FillRule, Gradient, GradientStop, MinMax, Path, Peaks, Radius, RasterOptions, Shape,
6 ShapeCache, StrokeStyle, Transform, WaveformOpts, bake_strip, layout_bars,
7 waveform_line_points,
8};
9
10use crate::track::Orientation;
11use crate::view::PaintCtx;
12
13fn line_style(width: f32) -> StrokeStyle {
14 StrokeStyle::new(width)
15}
16
17pub const GRADIENT_BAND_PX: f32 = 1.0;
18
19pub(crate) const GRADIENT_BANDS_MAX: usize = 256;
20
21pub(crate) fn gradient_band_count(extent: f32, band: f32) -> usize {
22 let band = if band.is_finite() && band > 0.0 {
23 band
24 } else {
25 GRADIENT_BAND_PX
26 };
27 ((extent / band).ceil().max(1.0) as usize).clamp(1, GRADIENT_BANDS_MAX)
28}
29
30pub(crate) fn band_edges(
31 origin: f32,
32 extent: f32,
33 count: usize,
34 snap: f32,
35 over: f32,
36) -> Vec<(f32, f32)> {
37 let count = count.max(1);
38 let snap = if snap.is_finite() && snap > 0.0 {
39 snap
40 } else {
41 1.0
42 };
43 let over = if over.is_finite() && over > 0.0 {
44 over
45 } else {
46 0.0
47 };
48 (0..count)
49 .map(|i| {
50 let last = i + 1 == count;
51 let lo = if i == 0 {
52 origin
53 } else {
54 ((origin + extent * i as f32 / count as f32) / snap).round() * snap
55 };
56 let mut hi = if last {
57 origin + extent
58 } else {
59 ((origin + extent * (i + 1) as f32 / count as f32) / snap).round() * snap + over
60 };
61 if hi < lo + 0.01 {
62 hi = lo + 0.01;
63 }
64 (lo, hi)
65 })
66 .collect()
67}
68
69impl PaintCtx<'_> {
70 pub fn shapes(&self) -> &ShapeCache {
71 self.shapes
72 }
73
74 pub fn raster_options(&self) -> RasterOptions {
75 RasterOptions::default().for_scale(self.scale_factor())
76 }
77
78 fn subpixel_draw_offset(&self) -> (Vec2, Vec2) {
79 let scale = self.scale_factor();
80 let scale = if scale.is_finite() && scale > 0.0 {
81 scale
82 } else {
83 1.0
84 };
85 let px = 1.0 / scale;
86 let step = px * 0.25;
87 let split = |v: f32| {
88 if !v.is_finite() {
89 return (0.0, 0.0);
90 }
91 let whole = (v / px).round() * px;
92 let sub = v - whole;
93 let sub = (sub / step).round() * step;
94 (whole, sub)
95 };
96 let (wx, sx) = split(self.draw_offset.x);
97 let (wy, sy) = split(self.draw_offset.y);
98 (Vec2::new(wx, wy), Vec2::new(sx, sy))
99 }
100
101 pub fn translate(&mut self, dx: f32, dy: f32) {
102 self.draw_offset.x += dx;
103 self.draw_offset.y += dy;
104 }
105
106 pub fn set_draw_offset(&mut self, offset: impl Into<Point>) {
107 let p = offset.into();
108 self.draw_offset = Vec2::new(p.x, p.y);
109 }
110
111 pub fn draw_offset(&self) -> Vec2 {
112 self.draw_offset
113 }
114
115 pub fn reset_draw_offset(&mut self) {
116 self.draw_offset = Vec2::ZERO;
117 }
118
119 pub fn save(&mut self) {
120 self.offset_stack.push(self.draw_offset);
121 }
122
123 pub fn restore(&mut self) {
124 if let Some(prev) = self.offset_stack.pop() {
125 self.draw_offset = prev;
126 }
127 }
128
129 pub fn path_from_svg(&self, data: &str) -> Option<Path> {
130 match Path::from_svg(data) {
131 Ok(path) => Some(path),
132 Err(err) => {
133 log::warn!("invalid SVG path: {err}");
134 None
135 }
136 }
137 }
138
139 pub fn rect(&mut self, rect: Rect, color: Color) {
140 self.quad(rect, color, 0.0);
141 }
142
143 pub fn rounded(&mut self, rect: Rect, radius: f32, color: Color) {
144 self.quad(rect, color, radius.max(0.0));
145 }
146
147 pub fn circle(&mut self, center: Point, radius: f32, color: Color) -> Rect {
148 self.disk(center, radius, color)
149 }
150
151 pub fn fill_gradient_rect(
152 &mut self,
153 rect: Rect,
154 stops: &[GradientStop],
155 orientation: Orientation,
156 ) -> Rect {
157 if rect.is_empty() || stops.is_empty() {
158 return Rect::ZERO;
159 }
160 let rect = self.shifted_rect(rect);
161 if rect.is_empty() {
162 return Rect::ZERO;
163 }
164 let gradient = match orientation {
165 Orientation::Horizontal => {
166 Gradient::linear((rect.left(), 0.0), (rect.right(), 0.0), stops.to_vec())
167 }
168 Orientation::Vertical => {
169 Gradient::linear((0.0, rect.top()), (0.0, rect.bottom()), stops.to_vec())
170 }
171 };
172 let mut bounds = Rect::ZERO;
173
174 let over = self.band_px(0.5);
175 match orientation {
176 Orientation::Horizontal => {
177 let mut x = rect.left();
178 while x < rect.right() {
179 let nx = (x + 1.0).min(rect.right());
180 let extra = if nx < rect.right() { over } else { 0.0 };
181 let c = gradient.color_at(Point::new((x + nx) * 0.5, rect.top()));
182 let band = Rect::from_xywh(x, rect.top(), nx - x + extra, rect.size.height);
183 self.list.push_quad(band, c, 0.0);
184 bounds = bounds.union(band);
185 x = nx;
186 }
187 }
188 Orientation::Vertical => {
189 let mut y = rect.top();
190 while y < rect.bottom() {
191 let ny = (y + 1.0).min(rect.bottom());
192 let extra = if ny < rect.bottom() { over } else { 0.0 };
193 let c = gradient.color_at(Point::new(rect.left(), (y + ny) * 0.5));
194 let band = Rect::from_xywh(rect.left(), y, rect.size.width, ny - y + extra);
195 self.list.push_quad(band, c, 0.0);
196 bounds = bounds.union(band);
197 y = ny;
198 }
199 }
200 }
201 bounds
202 }
203
204 pub fn knob(&mut self, center: Point, radius: f32, fill: Color) -> Rect {
205 if !(radius > 0.0) || !radius.is_finite() {
206 return Rect::ZERO;
207 }
208 let outer = self.disk(center, radius, Color::WHITE);
209 let inner_r = radius - 2.0;
210 if inner_r > 0.0 {
211 let inner = self.disk(center, inner_r, fill);
212 outer.union(inner)
213 } else {
214 outer
215 }
216 }
217
218 pub fn sv_field(&mut self, rect: Rect, hue01: f32) -> Rect {
219 if rect.is_empty() {
220 return Rect::ZERO;
221 }
222 let h = hue01.clamp(0.0, 1.0);
223 self.fill_rect(rect, Color::from_hsva(h, 1.0, 1.0, 1.0));
224 self.fill_gradient_rect(
225 rect,
226 &[
227 GradientStop::new(0.0, Color::WHITE),
228 GradientStop::new(1.0, Color::rgba(1.0, 1.0, 1.0, 0.0)),
229 ],
230 Orientation::Horizontal,
231 );
232 self.fill_gradient_rect(
233 rect,
234 &[
235 GradientStop::new(0.0, Color::rgba(0.0, 0.0, 0.0, 0.0)),
236 GradientStop::new(1.0, Color::BLACK),
237 ],
238 Orientation::Vertical,
239 );
240 self.shifted_rect(rect)
241 }
242
243 pub fn path(&mut self, path: &Path, width: f32, color: Color) -> Rect {
244 self.stroke(path, &line_style(width.max(0.0)), color)
245 }
246
247 pub fn text(&mut self, text: &str, size: f32, color: Color) {
248 let origin = (self.rect.origin.x, self.rect.origin.y);
249 self.draw_text(text, origin, size.max(0.0), color);
250 }
251
252 pub fn fill(&mut self, path: &Path, color: Color) -> Rect {
253 self.fill_rule(path, FillRule::NonZero, color)
254 }
255
256 pub fn fill_rule(&mut self, path: &Path, rule: FillRule, color: Color) -> Rect {
257 let opts = self.raster_options();
258 let (whole, sub) = self.subpixel_draw_offset();
259 let shape = self.shapes.fill_offset(path, rule, opts, sub);
260 self.draw_shape_offset(&shape, whole, color)
261 }
262
263 pub fn stroke(&mut self, path: &Path, style: &StrokeStyle, color: Color) -> Rect {
264 let opts = self.raster_options();
265 let (whole, sub) = self.subpixel_draw_offset();
266 let shape = self.shapes.stroke_offset(path, style, opts, sub);
267 self.draw_shape_offset(&shape, whole, color)
268 }
269
270 pub fn shape(&mut self, shape: &Shape, color: Color) -> Rect {
271 self.draw_shape(shape, Point::ZERO, color)
272 }
273
274 pub fn draw_shape(&mut self, shape: &Shape, at: Point, color: Color) -> Rect {
275 let offset = Vec2::new(self.draw_offset.x + at.x, self.draw_offset.y + at.y);
276 self.draw_shape_offset(shape, offset, color)
277 }
278
279 fn draw_shape_offset(&mut self, shape: &Shape, offset: Vec2, color: Color) -> Rect {
280 let mut bounds = Rect::ZERO;
281 for span in shape.iter() {
282 let rect = if offset.x == 0.0 && offset.y == 0.0 {
283 span.rect
284 } else {
285 Rect::from_xywh(
286 span.rect.origin.x + offset.x,
287 span.rect.origin.y + offset.y,
288 span.rect.size.width,
289 span.rect.size.height,
290 )
291 };
292 let color = Color {
293 a: color.a * span.alpha,
294 ..color
295 };
296 self.list.push_quad(rect, color, span.radius);
297 bounds = bounds.union(rect);
298 }
299 bounds
300 }
301
302 pub fn fill_gradient(&mut self, path: &Path, gradient: &Gradient) -> Rect {
303 self.fill_gradient_rule(path, FillRule::NonZero, gradient)
304 }
305
306 pub fn fill_gradient_rule(&mut self, path: &Path, rule: FillRule, gradient: &Gradient) -> Rect {
307 let opts = self.raster_options().analytic(false);
308 let (whole, sub) = self.subpixel_draw_offset();
309 let shape = self.shapes.fill_offset(path, rule, opts, sub);
310 let band = self.band_px(GRADIENT_BAND_PX);
311 let bands = self.shape_gradient_bands_offset(&shape, whole, gradient, band);
312 self.push_gradient_bands(&bands)
313 }
314
315 pub fn rounded_rect_gradient(&mut self, rect: Rect, radius: f32, gradient: &Gradient) -> Rect {
316 if rect.is_empty() {
317 return Rect::ZERO;
318 }
319 let mut path = Path::new();
320 path.rounded_rect(rect, radius);
321 self.fill_gradient(&path, gradient)
322 }
323
324 pub fn stroke_gradient(
325 &mut self,
326 path: &Path,
327 style: &StrokeStyle,
328 gradient: &Gradient,
329 ) -> Rect {
330 let opts = self.raster_options().analytic(false);
331 let (whole, sub) = self.subpixel_draw_offset();
332 let shape = self.shapes.stroke_offset(path, style, opts, sub);
333 let band = self.band_px(GRADIENT_BAND_PX);
334 let bands = self.shape_gradient_bands_offset(&shape, whole, gradient, band);
335 self.push_gradient_bands(&bands)
336 }
337
338 pub fn shadow(
339 &mut self,
340 rect: Rect,
341 radius: f32,
342 blur: f32,
343 offset: Point,
344 color: Color,
345 ) -> Rect {
346 let base = shift(rect, self.draw_offset);
347 if base.is_empty() || color.a <= 0.0 || !(blur > 0.0) {
348 return Rect::ZERO;
349 }
350 let shadow = Shadow {
351 rect: base,
352 color,
353 radius: shadow_radius(radius, base),
354 blur,
355 spread: 0.0,
356 offset: Vec2::new(offset.x, offset.y),
357 inset: false,
358 };
359 let bounds = shadow.bounds();
360 self.list.push_shadow(shadow);
361 bounds
362 }
363
364 pub fn inner_shadow(
365 &mut self,
366 rect: Rect,
367 radius: f32,
368 blur: f32,
369 offset: Point,
370 color: Color,
371 ) -> Rect {
372 let base = shift(rect, self.draw_offset);
373 if base.is_empty() || color.a <= 0.0 || !(blur > 0.0) {
374 return Rect::ZERO;
375 }
376 self.list.push_shadow(Shadow {
377 rect: base,
378 color,
379 radius: shadow_radius(radius, base),
380 blur,
381 spread: 0.0,
382 offset: Vec2::new(offset.x, offset.y),
383 inset: true,
384 });
385 base
386 }
387
388 pub fn shape_gradient_bands(
389 &self,
390 shape: &Shape,
391 at: Point,
392 gradient: &Gradient,
393 band: f32,
394 ) -> Vec<(Rect, Color, f32)> {
395 let offset = Vec2::new(self.draw_offset.x + at.x, self.draw_offset.y + at.y);
396 self.shape_gradient_bands_offset(shape, offset, gradient, band)
397 }
398
399 fn shape_gradient_bands_offset(
400 &self,
401 shape: &Shape,
402 offset: Vec2,
403 gradient: &Gradient,
404 band: f32,
405 ) -> Vec<(Rect, Color, f32)> {
406 let band = if band > 0.0 {
407 band
408 } else {
409 self.band_px(GRADIENT_BAND_PX)
410 };
411 let band = band.max(0.05);
412 let snap = self.band_px(1.0);
413 let vertical = gradient.is_vertical();
414 let mut out = Vec::new();
415 for span in shape.iter() {
416 let rect = shift(span.rect, offset);
417 if rect.is_empty() || span.alpha <= 0.0 {
418 continue;
419 }
420 if span.radius > 0.0 {
421 let center = Point::new(
422 rect.left() + rect.size.width * 0.5,
423 rect.top() + rect.size.height * 0.5,
424 );
425 let color = gradient.color_at(center);
426 out.push((rect, tint(color, span.alpha), span.radius));
427 continue;
428 }
429 let origin = if vertical { rect.top() } else { rect.left() };
430 let extent = if vertical {
431 rect.size.height
432 } else {
433 rect.size.width
434 };
435 let count = gradient_band_count(extent, band);
436
437 for (lo, hi) in band_edges(origin, extent, count, snap, snap * 0.5) {
438 if hi <= lo {
439 continue;
440 }
441 let sub = if vertical {
442 Rect::from_xywh(rect.left(), lo, rect.size.width, hi - lo)
443 } else {
444 Rect::from_xywh(lo, rect.top(), hi - lo, rect.size.height)
445 };
446 let color = gradient.color_at(Point::new(
447 sub.left() + sub.size.width * 0.5,
448 sub.top() + sub.size.height * 0.5,
449 ));
450 out.push((sub, tint(color, span.alpha), 0.0));
451 }
452 }
453 out
454 }
455
456 pub fn push_gradient_bands(&mut self, bands: &[(Rect, Color, f32)]) -> Rect {
457 let mut bounds = Rect::ZERO;
458 for (rect, color, radius) in bands {
459 if rect.is_empty() {
460 continue;
461 }
462 self.list.push_quad(*rect, *color, *radius);
463 bounds = bounds.union(*rect);
464 }
465 bounds
466 }
467
468 pub fn draw_shape_gradient(
469 &mut self,
470 shape: &Shape,
471 at: Point,
472 gradient: &Gradient,
473 band: f32,
474 ) -> Rect {
475 let bands = self.shape_gradient_bands(shape, at, gradient, band);
476 self.push_gradient_bands(&bands)
477 }
478
479 pub fn shape_gradient(&mut self, shape: &Shape, gradient: &Gradient) -> Rect {
480 let band = self.band_px(GRADIENT_BAND_PX);
481 self.draw_shape_gradient(shape, Point::ZERO, gradient, band)
482 }
483
484 pub fn fill_rect(&mut self, rect: Rect, color: Color) -> Rect {
485 let rect = self.shifted_rect(rect);
486 self.list.push_quad(rect, color, 0.0);
487 rect
488 }
489
490 pub fn bars(&mut self, bars: &[Rect], color: Color) -> Rect {
491 if bars.is_empty() || color.a <= 0.0 {
492 return Rect::ZERO;
493 }
494 let off = self.draw_offset;
495 let mut bounds = Rect::ZERO;
496 if off.x == 0.0 && off.y == 0.0 {
497 self.list.push_waveform(bars, color);
498 for r in bars {
499 bounds = bounds.union(*r);
500 }
501 } else {
502 let shifted: Vec<Rect> = bars
503 .iter()
504 .map(|r| {
505 Rect::from_xywh(
506 r.left() + off.x,
507 r.top() + off.y,
508 r.size.width,
509 r.size.height,
510 )
511 })
512 .collect();
513 self.list.push_waveform(&shifted, color);
514 for r in &shifted {
515 bounds = bounds.union(*r);
516 }
517 }
518 bounds
519 }
520
521 pub fn waveform(
522 &mut self,
523 area: Rect,
524 peaks: &[MinMax],
525 opts: WaveformOpts,
526 color: Color,
527 ) -> Rect {
528 if area.is_empty() || peaks.is_empty() || color.a <= 0.0 {
529 return Rect::ZERO;
530 }
531 let area = self.shifted_rect(area);
532 let bars = layout_bars(area, peaks, opts);
533 if bars.is_empty() {
534 return Rect::ZERO;
535 }
536 self.list.push_waveform(&bars, color);
537 area
538 }
539
540 pub fn waveform_window(
541 &mut self,
542 area: Rect,
543 peaks: &Peaks,
544 start_peak: usize,
545 count: usize,
546 opts: WaveformOpts,
547 color: Color,
548 ) -> (Rect, usize) {
549 if area.is_empty() || peaks.is_empty() || color.a <= 0.0 {
550 return (Rect::ZERO, 0);
551 }
552 let width = (area.size.width.ceil() as usize).clamp(1, 8192);
553 let (level, cols) = peaks.query_for_width(start_peak, count, width);
554 if cols.is_empty() {
555 return (Rect::ZERO, level);
556 }
557 let bounds = self.waveform(area, &cols, opts, color);
558 (bounds, level)
559 }
560
561 pub fn waveform_line(
562 &mut self,
563 area: Rect,
564 peaks: &[MinMax],
565 width: f32,
566 color: Color,
567 ) -> Rect {
568 if area.is_empty() || peaks.is_empty() || color.a <= 0.0 {
569 return Rect::ZERO;
570 }
571 let pts = waveform_line_points(area, peaks);
572 if pts.len() < 2 {
573 return Rect::ZERO;
574 }
575 let mut path = Path::new();
576 path.polyline(&pts);
577 let opts = self.raster_options();
578 let shape = self
579 .shapes
580 .stroke(&path, &StrokeStyle::round(width.clamp(0.5, 8.0)), opts);
581 let off = self.draw_offset;
582 let mut rects = Vec::with_capacity(shape.len().min(16384));
583 let mut bounds = Rect::ZERO;
584 for span in shape.iter() {
585 let r = span.rect;
586 let r = if off.x == 0.0 && off.y == 0.0 {
587 r
588 } else {
589 Rect::from_xywh(
590 r.left() + off.x,
591 r.top() + off.y,
592 r.size.width,
593 r.size.height,
594 )
595 };
596 if r.is_empty() {
597 continue;
598 }
599 bounds = bounds.union(r);
600 rects.push(r);
601 if rects.len() >= 16384 {
602 break;
603 }
604 }
605 if rects.is_empty() {
606 return Rect::ZERO;
607 }
608 self.list.push_waveform(&rects, color);
609 bounds
610 }
611
612 pub fn waveform_image(
613 &mut self,
614 area: Rect,
615 peaks: &[MinMax],
616 fg: Color,
617 bg: Color,
618 id: u64,
619 version: u64,
620 ) -> Rect {
621 if area.is_empty() || peaks.is_empty() {
622 return Rect::ZERO;
623 }
624 let w = (area.size.width.ceil() as u32).clamp(8, 4096);
625 let h = (area.size.height.ceil() as u32).clamp(8, 1024);
626 let cols: Vec<MinMax> = if peaks.len() == w as usize {
627 peaks.to_vec()
628 } else {
629 let n = peaks.len();
630 let mut out = Vec::with_capacity(w as usize);
631 for i in 0..(w as usize) {
632 let a = (i * n) / (w as usize);
633 let mut b = ((i + 1) * n) / (w as usize);
634 if b <= a {
635 b = (a + 1).min(n);
636 }
637 let mut m = peaks[a];
638 for &v in &peaks[a + 1..b] {
639 m = m.merge(v);
640 }
641 out.push(m);
642 }
643 out
644 };
645 let data = bake_strip(&cols, w, h, fg, bg);
646 if data.is_empty() {
647 return Rect::ZERO;
648 }
649 let area = self.shifted_rect(area);
650 self.list.push_image(
651 area,
652 [0.0, 0.0, 1.0, 1.0],
653 ImageSource::Dynamic {
654 id,
655 width: w,
656 height: h,
657 data: Arc::new(data),
658 version,
659 },
660 );
661 area
662 }
663
664 pub fn disk(&mut self, center: Point, radius: f32, color: Color) -> Rect {
665 if !(radius > 0.0) {
666 return Rect::ZERO;
667 }
668 let shape = self.shapes.disk(center, radius);
669 self.draw_shape(&shape, Point::ZERO, color)
670 }
671
672 pub fn rounded_rect(&mut self, rect: Rect, radius: f32, color: Color) -> Rect {
673 let shape = self.shapes.rounded_rect(rect, radius);
674 self.draw_shape(&shape, Point::ZERO, color)
675 }
676
677 pub fn ellipse(&mut self, center: Point, rx: f32, ry: f32, color: Color) -> Rect {
678 if !(rx > 0.0) || !(ry > 0.0) {
679 return Rect::ZERO;
680 }
681 let mut path = Path::new();
682 path.ellipse(center, rx, ry);
683 self.fill(&path, color)
684 }
685
686 pub fn ellipse_outline(
687 &mut self,
688 center: Point,
689 rx: f32,
690 ry: f32,
691 style: &StrokeStyle,
692 color: Color,
693 ) -> Rect {
694 if !(rx > 0.0) || !(ry > 0.0) {
695 return Rect::ZERO;
696 }
697 let mut path = Path::new();
698 path.ellipse(center, rx, ry);
699 self.stroke(&path, style, color)
700 }
701
702 pub fn rect_outline(&mut self, rect: Rect, style: &StrokeStyle, color: Color) -> Rect {
703 if rect.is_empty() {
704 return Rect::ZERO;
705 }
706 let mut path = Path::new();
707 path.rect(rect);
708 self.stroke(&path, style, color)
709 }
710
711 pub fn rounded_rect_outline(
712 &mut self,
713 rect: Rect,
714 radius: f32,
715 style: &StrokeStyle,
716 color: Color,
717 ) -> Rect {
718 if rect.is_empty() {
719 return Rect::ZERO;
720 }
721 let mut path = Path::new();
722 path.rounded_rect(rect, radius);
723 self.stroke(&path, style, color)
724 }
725
726 pub fn rounded_rect_corners(
727 &mut self,
728 rect: Rect,
729 radii: impl Into<Radius>,
730 color: Color,
731 ) -> Rect {
732 if rect.is_empty() {
733 return Rect::ZERO;
734 }
735 let mut path = Path::new();
736 path.rounded_rect(rect, radii);
737 self.fill(&path, color)
738 }
739
740 pub fn rounded_rect_corners_outline(
741 &mut self,
742 rect: Rect,
743 radii: impl Into<Radius>,
744 style: &StrokeStyle,
745 color: Color,
746 ) -> Rect {
747 if rect.is_empty() {
748 return Rect::ZERO;
749 }
750 let mut path = Path::new();
751 path.rounded_rect(rect, radii);
752 self.stroke(&path, style, color)
753 }
754
755 pub fn capsule(&mut self, from: Point, to: Point, radius: f32, color: Color) -> Rect {
756 if !(radius > 0.0) {
757 return Rect::ZERO;
758 }
759 if from == to {
760 return self.disk(from, radius, color);
761 }
762 let style = StrokeStyle::round(radius * 2.0);
763 self.line_styled(from, to, &style, color)
764 }
765
766 pub fn arrow(
767 &mut self,
768 from: Point,
769 to: Point,
770 width: f32,
771 head_len: f32,
772 head_width: f32,
773 color: Color,
774 ) -> Rect {
775 self.arrow_styled(from, to, &line_style(width), head_len, head_width, color)
776 }
777
778 pub fn arrow_styled(
779 &mut self,
780 from: Point,
781 to: Point,
782 style: &StrokeStyle,
783 head_len: f32,
784 head_width: f32,
785 color: Color,
786 ) -> Rect {
787 let dx = to.x - from.x;
788 let dy = to.y - from.y;
789 let len = (dx * dx + dy * dy).sqrt();
790 if !(len > 1e-6) {
791 return Rect::ZERO;
792 }
793 let (ux, uy) = (dx / len, dy / len);
794 let hl = head_len.clamp(0.0, len);
795 let hw = head_width.max(0.0);
796 let mut bounds = Rect::ZERO;
797 if hl <= 0.0 || hw <= 0.0 {
798 return self.line_styled(from, to, style, color);
799 }
800 let base = Point::new(to.x - ux * hl, to.y - uy * hl);
801 if len - hl > 1e-4 {
802 bounds = bounds.union(self.line_styled(from, base, style, color));
803 }
804 let (px, py) = (-uy, ux);
805 let mut head = Path::new();
806 head.polygon(&[
807 to,
808 Point::new(base.x + px * hw * 0.5, base.y + py * hw * 0.5),
809 Point::new(base.x - px * hw * 0.5, base.y - py * hw * 0.5),
810 ]);
811 bounds.union(self.fill(&head, color))
812 }
813
814 pub fn cross(&mut self, center: Point, half: f32, width: f32, color: Color) -> Rect {
815 if !(half > 0.0) {
816 return Rect::ZERO;
817 }
818 let style = StrokeStyle::round(width);
819 let h = self.line_styled(
820 Point::new(center.x - half, center.y),
821 Point::new(center.x + half, center.y),
822 &style,
823 color,
824 );
825 let v = self.line_styled(
826 Point::new(center.x, center.y - half),
827 Point::new(center.x, center.y + half),
828 &style,
829 color,
830 );
831 h.union(v)
832 }
833
834 pub fn x_marker(&mut self, center: Point, half: f32, width: f32, color: Color) -> Rect {
835 if !(half > 0.0) {
836 return Rect::ZERO;
837 }
838 let style = StrokeStyle::round(width);
839 let a = self.line_styled(
840 Point::new(center.x - half, center.y - half),
841 Point::new(center.x + half, center.y + half),
842 &style,
843 color,
844 );
845 let b = self.line_styled(
846 Point::new(center.x - half, center.y + half),
847 Point::new(center.x + half, center.y - half),
848 &style,
849 color,
850 );
851 a.union(b)
852 }
853
854 pub fn regular_polygon(
855 &mut self,
856 center: Point,
857 radius: f32,
858 sides: u32,
859 rotation_deg: f32,
860 color: Color,
861 ) -> Rect {
862 let pts = regular_corners(center, radius, sides, rotation_deg);
863 if pts.is_empty() {
864 return Rect::ZERO;
865 }
866 self.polygon(&pts, color)
867 }
868
869 pub fn regular_polygon_outline(
870 &mut self,
871 center: Point,
872 radius: f32,
873 sides: u32,
874 rotation_deg: f32,
875 style: &StrokeStyle,
876 color: Color,
877 ) -> Rect {
878 let pts = regular_corners(center, radius, sides, rotation_deg);
879 if pts.is_empty() {
880 return Rect::ZERO;
881 }
882 let mut path = Path::new();
883 path.polygon(&pts);
884 self.stroke(&path, style, color)
885 }
886
887 pub fn star(
888 &mut self,
889 center: Point,
890 outer_r: f32,
891 inner_r: f32,
892 points: u32,
893 rotation_deg: f32,
894 color: Color,
895 ) -> Rect {
896 let pts = star_corners(center, outer_r, inner_r, points, rotation_deg);
897 if pts.is_empty() {
898 return Rect::ZERO;
899 }
900 self.polygon(&pts, color)
901 }
902
903 pub fn star_outline(
904 &mut self,
905 center: Point,
906 outer_r: f32,
907 inner_r: f32,
908 points: u32,
909 rotation_deg: f32,
910 style: &StrokeStyle,
911 color: Color,
912 ) -> Rect {
913 let pts = star_corners(center, outer_r, inner_r, points, rotation_deg);
914 if pts.is_empty() {
915 return Rect::ZERO;
916 }
917 let mut path = Path::new();
918 path.polygon(&pts);
919 self.stroke(&path, style, color)
920 }
921
922 pub fn ring(&mut self, center: Point, radius: f32, width: f32, color: Color) -> Rect {
923 let mut path = Path::new();
924 path.circle(center, radius);
925 self.stroke(&path, &StrokeStyle::new(width), color)
926 }
927
928 pub fn arc(
929 &mut self,
930 center: Point,
931 radius: f32,
932 from_deg: f32,
933 to_deg: f32,
934 width: f32,
935 color: Color,
936 ) -> Rect {
937 let mut path = Path::new();
938 path.arc(center, radius, from_deg, to_deg);
939 self.stroke(&path, &line_style(width), color)
940 }
941
942 pub fn sector(
943 &mut self,
944 center: Point,
945 radius: f32,
946 from_deg: f32,
947 to_deg: f32,
948 color: Color,
949 ) -> Rect {
950 if !(radius > 0.0) || !from_deg.is_finite() || !to_deg.is_finite() {
951 return Rect::ZERO;
952 }
953 if (to_deg - from_deg).abs() < 1e-6 {
954 return Rect::ZERO;
955 }
956 let mut path = Path::new();
957 path.move_to(center);
958 path.arc(center, radius, from_deg, to_deg);
959 path.close();
960 self.fill(&path, color)
961 }
962
963 pub fn donut_sector(
964 &mut self,
965 center: Point,
966 inner_r: f32,
967 outer_r: f32,
968 from_deg: f32,
969 to_deg: f32,
970 color: Color,
971 ) -> Rect {
972 if !(outer_r > 0.0) || !from_deg.is_finite() || !to_deg.is_finite() {
973 return Rect::ZERO;
974 }
975 if (to_deg - from_deg).abs() < 1e-6 {
976 return Rect::ZERO;
977 }
978 let inner = if inner_r.is_finite() {
979 inner_r.clamp(0.0, outer_r)
980 } else {
981 0.0
982 };
983 if inner <= 0.0 {
984 return self.sector(center, outer_r, from_deg, to_deg, color);
985 }
986 let (a, b) = (from_deg.to_radians(), to_deg.to_radians());
987 let outer_from = Point::new(center.x + a.cos() * outer_r, center.y + a.sin() * outer_r);
988 let inner_to = Point::new(center.x + b.cos() * inner, center.y + b.sin() * inner);
989 let mut path = Path::new();
990 path.move_to(outer_from);
991 path.arc(center, outer_r, from_deg, to_deg);
992 path.line_to(inner_to);
993 path.arc(center, inner, to_deg, from_deg);
994 path.close();
995 self.fill(&path, color)
996 }
997
998 pub fn line(&mut self, from: Point, to: Point, width: f32, color: Color) -> Rect {
999 let (from, to) = crisp_axis_line(from, to, width, self.scale_factor());
1000 self.line_styled(from, to, &line_style(width), color)
1001 }
1002
1003 pub fn line_crisp(&mut self, from: Point, to: Point, width: f32, color: Color) -> Rect {
1004 let (from, to) = crisp_axis_line(from, to, width, self.scale_factor());
1005 self.line(from, to, width, color)
1006 }
1007
1008 pub fn dotted_line(
1009 &mut self,
1010 from: Point,
1011 to: Point,
1012 width: f32,
1013 spacing: f32,
1014 color: Color,
1015 ) -> Rect {
1016 let radius = width * 0.5;
1017 if !(radius > 0.0) || !(spacing > 0.0) || !spacing.is_finite() {
1018 return Rect::ZERO;
1019 }
1020 let dx = to.x - from.x;
1021 let dy = to.y - from.y;
1022 let len = (dx * dx + dy * dy).sqrt();
1023 if !(len > 1e-6) {
1024 return self.disk(from, radius, color);
1025 }
1026
1027 const MAX_DOTS: usize = 4096;
1028 let count = ((len / spacing).floor() as usize).min(MAX_DOTS);
1029 let mut bounds = Rect::ZERO;
1030 for i in 0..=count {
1031 let t = (i as f32 * spacing).min(len);
1032 let k = t / len;
1033 let p = Point::new(from.x + dx * k, from.y + dy * k);
1034 bounds = bounds.union(self.disk(p, radius, color));
1035 }
1036
1037 if (count as f32 * spacing - len).abs() > 1e-3 {
1038 bounds = bounds.union(self.disk(to, radius, color));
1039 }
1040 bounds
1041 }
1042
1043 pub fn line_styled(
1044 &mut self,
1045 from: Point,
1046 to: Point,
1047 style: &StrokeStyle,
1048 color: Color,
1049 ) -> Rect {
1050 let mut path = Path::new();
1051 path.move_to(from).line_to(to);
1052 self.stroke(&path, style, color)
1053 }
1054
1055 pub fn dashed_line(
1056 &mut self,
1057 from: Point,
1058 to: Point,
1059 width: f32,
1060 dash: impl Into<Dash>,
1061 color: Color,
1062 ) -> Rect {
1063 let style = line_style(width).dash(dash);
1064 self.line_styled(from, to, &style, color)
1065 }
1066
1067 pub fn outline(&mut self, points: &[Point], style: &StrokeStyle, color: Color) -> Rect {
1068 let mut path = Path::new();
1069 path.polyline(points);
1070 self.stroke(&path, style, color)
1071 }
1072
1073 pub fn polygon(&mut self, points: &[Point], color: Color) -> Rect {
1074 let mut path = Path::new();
1075 path.polygon(points);
1076 self.fill(&path, color)
1077 }
1078
1079 pub fn spline(
1080 &mut self,
1081 points: &[Point],
1082 tension: f32,
1083 style: &StrokeStyle,
1084 color: Color,
1085 ) -> Rect {
1086 let mut path = Path::new();
1087 path.spline(points, tension);
1088 self.stroke(&path, style, color)
1089 }
1090
1091 pub fn curve(
1092 &mut self,
1093 from: Point,
1094 c1: Point,
1095 c2: Point,
1096 to: Point,
1097 style: &StrokeStyle,
1098 color: Color,
1099 ) -> Rect {
1100 let mut path = Path::new();
1101 path.move_to(from).cubic_to(c1, c2, to);
1102 self.stroke(&path, style, color)
1103 }
1104
1105 pub fn curve_quad(
1106 &mut self,
1107 from: Point,
1108 control: Point,
1109 to: Point,
1110 style: &StrokeStyle,
1111 color: Color,
1112 ) -> Rect {
1113 let mut path = Path::new();
1114 path.move_to(from).quad_to(control, to);
1115 self.stroke(&path, style, color)
1116 }
1117
1118 pub fn draw_svg(&mut self, data: &str, style: Option<&StrokeStyle>, color: Color) -> Rect {
1119 let Some(path) = self.path_from_svg(data) else {
1120 return Rect::ZERO;
1121 };
1122 match style {
1123 Some(style) => self.stroke(&path, style, color),
1124 None => self.fill(&path, color),
1125 }
1126 }
1127
1128 pub fn draw_transformed(
1129 &mut self,
1130 path: &Path,
1131 transform: Transform,
1132 style: Option<&StrokeStyle>,
1133 color: Color,
1134 ) -> Rect {
1135 let path = path.transformed(transform);
1136 match style {
1137 Some(style) => self.stroke(&path, style, color),
1138 None => self.fill(&path, color),
1139 }
1140 }
1141
1142 fn shifted_rect(&self, rect: Rect) -> Rect {
1143 if self.draw_offset.x == 0.0 && self.draw_offset.y == 0.0 {
1144 rect
1145 } else {
1146 Rect::from_xywh(
1147 rect.origin.x + self.draw_offset.x,
1148 rect.origin.y + self.draw_offset.y,
1149 rect.size.width,
1150 rect.size.height,
1151 )
1152 }
1153 }
1154}
1155
1156impl PaintCtx<'_> {
1157 fn band_px(&self, px: f32) -> f32 {
1158 let scale = self.scale_factor();
1159 let scale = if scale.is_finite() && scale > 0.0 {
1160 scale
1161 } else {
1162 1.0
1163 };
1164 px.max(0.05) / scale
1165 }
1166}
1167
1168fn shadow_radius(radius: f32, rect: Rect) -> f32 {
1169 radius
1170 .clamp(0.0, rect.size.width.min(rect.size.height) * 0.5)
1171 .max(0.0)
1172}
1173
1174pub(crate) fn crisp_axis_line(from: Point, to: Point, width: f32, scale: f32) -> (Point, Point) {
1175 let s = if scale.is_finite() && scale > 0.0 {
1176 scale
1177 } else {
1178 1.0
1179 };
1180 let px = 1.0 / s;
1181 let width_px = width * s;
1182 if !width.is_finite() || width <= 0.0 || (width_px - width_px.round()).abs() > 0.01 {
1183 return (from, to);
1184 }
1185 let odd = (width_px.round() as i64) % 2 != 0;
1186 let snap = |v: f32| {
1187 if odd {
1188 ((v / px) - 0.5).round() * px + px * 0.5
1189 } else {
1190 (v / px).round() * px
1191 }
1192 };
1193 let dx = (to.x - from.x).abs();
1194 let dy = (to.y - from.y).abs();
1195 if dy <= 1e-4 && dx > 1e-4 {
1196 let y = snap(from.y);
1197 (Point::new(from.x, y), Point::new(to.x, y))
1198 } else if dx <= 1e-4 && dy > 1e-4 {
1199 let x = snap(from.x);
1200 (Point::new(x, from.y), Point::new(x, to.y))
1201 } else {
1202 (from, to)
1203 }
1204}
1205
1206fn shift(rect: Rect, offset: Vec2) -> Rect {
1207 if offset.x == 0.0 && offset.y == 0.0 {
1208 rect
1209 } else {
1210 Rect::from_xywh(
1211 rect.origin.x + offset.x,
1212 rect.origin.y + offset.y,
1213 rect.size.width,
1214 rect.size.height,
1215 )
1216 }
1217}
1218
1219fn tint(color: Color, alpha: f32) -> Color {
1220 Color {
1221 a: color.a * alpha,
1222 ..color
1223 }
1224}
1225
1226fn regular_corners(center: Point, radius: f32, sides: u32, rotation_deg: f32) -> Vec<Point> {
1227 if !(radius > 0.0) {
1228 return Vec::new();
1229 }
1230 let n = sides.clamp(3, 64);
1231 let base = rotation_deg.to_radians();
1232 (0..n)
1233 .map(|i| {
1234 let a = base + i as f32 * std::f32::consts::TAU / n as f32;
1235 Point::new(center.x + a.cos() * radius, center.y + a.sin() * radius)
1236 })
1237 .collect()
1238}
1239
1240fn star_corners(
1241 center: Point,
1242 outer_r: f32,
1243 inner_r: f32,
1244 points: u32,
1245 rotation_deg: f32,
1246) -> Vec<Point> {
1247 if !(outer_r > 0.0) || !(inner_r > 0.0) {
1248 return Vec::new();
1249 }
1250
1251 let n = points.clamp(2, 64);
1252 let base = rotation_deg.to_radians();
1253 (0..2 * n)
1254 .map(|i| {
1255 let r = if i % 2 == 0 { outer_r } else { inner_r };
1256 let a = base + i as f32 * std::f32::consts::PI / n as f32;
1257 Point::new(center.x + a.cos() * r, center.y + a.sin() * r)
1258 })
1259 .collect()
1260}
1261
1262#[cfg(test)]
1263mod tests {
1264 use super::*;
1265
1266 #[test]
1267 fn gradient_bands_capped_per_span() {
1268 assert_eq!(gradient_band_count(10.0, 1.0), 10);
1269 assert_eq!(
1270 gradient_band_count(800.0, 1.0),
1271 GRADIENT_BANDS_MAX,
1272 "800px cover is max 256 quads per span, not 800"
1273 );
1274 assert_eq!(gradient_band_count(0.0, 1.0), 1, "zawsze co najmniej 1");
1275 assert_eq!(gradient_band_count(-5.0, 1.0), 1);
1276 assert_eq!(
1277 gradient_band_count(100.0, 0.0),
1278 gradient_band_count(100.0, GRADIENT_BAND_PX),
1279 "bad band returns to default"
1280 );
1281 }
1282
1283 #[test]
1284 fn gradient_bands_cover_span_on_fractional_origin() {
1285 let e = band_edges(100.5, 198.0, 198, 1.0, 0.5);
1286 assert_eq!(e.len(), 198);
1287 assert!((e[0].0 - 100.5).abs() < 1e-6, "pierwszy pas od originu");
1288 assert!(
1289 (e.last().unwrap().1 - 298.5).abs() < 1e-6,
1290 "last to the end"
1291 );
1292 for (i, w) in e.windows(2).enumerate() {
1293 let phi = w[0].1;
1294 let (lo, hi) = (w[1].0, w[1].1);
1295 assert!(lo <= phi + 1e-4, "dziura przed pasem {i}");
1296 assert!((lo - lo.round()).abs() < 1e-4, "edge {i} outside mesh");
1297
1298 assert!(hi - lo > 0.0 && hi - lo <= 2.6, "wrong belt size {i}");
1299 }
1300
1301 let e2 = band_edges(100.25, 50.0, 50, 0.5, 0.25);
1302 assert_eq!(e2.len(), 50);
1303 for w in e2.windows(2) {
1304 assert!(w[1].0 <= w[0].1 + 1e-4);
1305 assert!(((w[1].0 * 2.0).round() - w[1].0 * 2.0).abs() < 1e-3);
1306 }
1307 }
1308
1309 #[test]
1310 fn star_corner_count_is_clamped() {
1311 let c = Point::new(0.0, 0.0);
1312 assert_eq!(
1313 star_corners(c, 10.0, 5.0, u32::MAX, 0.0).len(),
1314 128,
1315 "top clamp 64 arms"
1316 );
1317 assert_eq!(star_corners(c, 10.0, 5.0, 65, 0.0).len(), 128);
1318 assert_eq!(star_corners(c, 10.0, 5.0, 5, 0.0).len(), 10);
1319 assert_eq!(star_corners(c, 10.0, 5.0, 1, 0.0).len(), 4, "dolny clamp 2");
1320 assert_eq!(star_corners(c, 10.0, 5.0, 0, 0.0).len(), 4);
1321 }
1322}