Skip to main content

sequoia_view/
paint_draw.rs

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}