1#![forbid(unsafe_code)]
2
3use sequoia_core::{
4 BrushStroke, Color, Fill, GradientKind, GradientStops, ImageSampling, ImageSource, Pixels,
5 Point, Rect, Size, Transform, Vec2,
6};
7use sequoia_text::FontWeight;
8
9use crate::paint_draw::crisp_axis_line;
10use crate::view::PaintCtx;
11
12pub struct Painter<'a, 'b> {
13 ctx: &'a mut PaintCtx<'b>,
14 origin: Point,
15 size: Size,
16
17 transform: Transform,
18}
19
20impl<'a, 'b> Painter<'a, 'b> {
21 pub fn new(ctx: &'a mut PaintCtx<'b>) -> Self {
22 debug_assert!(
23 ctx.draw_offset.x == 0.0 && ctx.draw_offset.y == 0.0,
24 "Painter not using draw_offset - remove translate/set_draw_offset"
25 );
26 let origin = ctx.rect.origin;
27 let size = ctx.rect.size;
28 Self {
29 ctx,
30 origin,
31 size,
32 transform: Transform::IDENTITY,
33 }
34 }
35
36 pub fn size(&self) -> Size {
37 self.size
38 }
39
40 pub fn rect(&self) -> Rect {
41 Rect::new(Point::ZERO, self.size)
42 }
43
44 pub fn origin_global(&self) -> Point {
45 self.origin
46 }
47
48 pub fn ctx(&mut self) -> &mut PaintCtx<'b> {
49 self.ctx
50 }
51
52 pub fn push_list_transform(&mut self, t: Transform) {
53 self.ctx.list.push_transform(t);
54 }
55
56 pub fn pop_list_transform(&mut self) {
57 self.ctx.list.pop_transform();
58 }
59
60 pub fn with_transform<R>(&mut self, t: Transform, f: impl FnOnce(&mut Self) -> R) -> R {
61 let prev = self.transform;
62 self.transform = t.then(prev);
63 let r = f(self);
64 self.transform = prev;
65 r
66 }
67
68 pub fn unproject(&self, p: Point) -> Option<Point> {
69 let inv = self.transform.inverse()?;
70 Some(inv.apply(Point::new(p.x - self.origin.x, p.y - self.origin.y)))
71 }
72
73 fn unit_scale(&self) -> f32 {
74 self.transform.apply_vec(Vec2::new(1.0, 0.0)).length()
75 }
76
77 fn stroke_style(&self, stroke: BrushStroke) -> sequoia_draw::StrokeStyle {
78 use sequoia_draw::StrokeStyle;
79 let w = (stroke.width * self.unit_scale()).max(0.0);
80 if stroke.rounded {
81 StrokeStyle::round(w)
82 } else {
83 StrokeStyle::new(w)
84 }
85 }
86
87 pub fn to_global_rect(&self, r: Rect) -> Rect {
88 if r.is_empty() {
89 return r;
90 }
91 let r = self.transform.apply_rect_aabb(r);
92 Rect::from_xywh(
93 r.left() + self.origin.x,
94 r.top() + self.origin.y,
95 r.size.width,
96 r.size.height,
97 )
98 }
99
100 pub fn to_global_point(&self, p: Point) -> Point {
101 let p = self.transform.apply(p);
102 Point::new(p.x + self.origin.x, p.y + self.origin.y)
103 }
104
105 pub fn to_local_point(&self, p: Point) -> Point {
106 Point::new(p.x - self.origin.x, p.y - self.origin.y)
107 }
108
109 pub fn fill(&mut self, rect: Rect, fill: Fill) -> Rect {
110 self.rounded(rect, 0.0, fill)
111 }
112
113 pub fn rounded(&mut self, rect: Rect, radius: f32, fill: Fill) -> Rect {
114 if rect.is_empty() || !fill.is_visible() {
115 return Rect::ZERO;
116 }
117 let global = self.to_global_rect(rect);
118
119 let radius = radius * self.unit_scale();
120 let radius = if radius.is_finite() {
121 radius
122 .clamp(0.0, global.size.width.min(global.size.height) * 0.5)
123 .max(0.0)
124 } else {
125 0.0
126 };
127 match fill {
128 Fill::Solid(color) => {
129 if color.a <= 0.0 {
130 return Rect::ZERO;
131 }
132 self.ctx.list.push_quad(global, color, radius);
133 rect
134 }
135 Fill::Gradient { kind, stops } => self.push_gradient(rect, radius, kind, stops),
136 }
137 }
138
139 pub fn circle(&mut self, center: Point, radius: f32, fill: Fill) -> Rect {
140 if !(radius > 0.0) || !radius.is_finite() || !fill.is_visible() {
141 return Rect::ZERO;
142 }
143 let rect = Rect::from_xywh(
144 center.x - radius,
145 center.y - radius,
146 radius * 2.0,
147 radius * 2.0,
148 );
149
150 let gr = radius * self.unit_scale();
151 if !(gr > 0.0) {
152 return Rect::ZERO;
153 }
154 match fill {
155 Fill::Solid(color) => {
156 if color.a <= 0.0 {
157 return Rect::ZERO;
158 }
159 let global = self.to_global_rect(rect);
160 self.ctx.list.push_quad(global, color, gr);
161 rect
162 }
163 Fill::Gradient { kind, stops } => {
164 let kind = match kind {
165 GradientKind::Radial { radius: r } if r <= 0.0 => {
166 GradientKind::Radial { radius: gr }
167 }
168 other => other,
169 };
170 self.push_gradient(rect, gr, kind, stops)
171 }
172 }
173 }
174
175 pub fn ring(&mut self, center: Point, radius: f32, width: f32, color: Color) -> Rect {
176 if !(radius > 0.0) || !(width > 0.0) || !width.is_finite() || color.a <= 0.0 {
177 return Rect::ZERO;
178 }
179 let k = self.unit_scale();
180 let mut path = sequoia_draw::Path::new();
181 path.circle(self.to_global_point(center), radius * k);
182 let bounds = self
183 .ctx
184 .stroke(&path, &self.stroke_style(BrushStroke::new(width)), color);
185 self.to_local_bounds(bounds)
186 }
187
188 pub fn line(&mut self, from: Point, to: Point, stroke: BrushStroke, color: Color) -> Rect {
189 if !stroke.is_visible() || color.a <= 0.0 {
190 return Rect::ZERO;
191 }
192 let style = self.stroke_style(stroke);
193 let from = self.to_global_point(from);
194 let to = self.to_global_point(to);
195 let (from, to) = crisp_axis_line(
196 from,
197 to,
198 stroke.width * self.unit_scale(),
199 self.ctx.scale_factor(),
200 );
201 let mut path = sequoia_draw::Path::new();
202 path.move_to(from).line_to(to);
203 let bounds = self.ctx.stroke(&path, &style, color);
204 self.to_local_bounds(bounds)
205 }
206
207 pub fn polyline(&mut self, points: &[Point], stroke: BrushStroke, color: Color) -> Rect {
208 if points.len() < 2 || !stroke.is_visible() || color.a <= 0.0 {
209 return Rect::ZERO;
210 }
211 use sequoia_draw::Path;
212 let style = self.stroke_style(stroke);
213 let mut path = Path::new();
214 let mut it = points.iter();
215
216 path.move_to(self.to_global_point(*it.next().unwrap_or(&Point::ZERO)));
217 for p in it {
218 path.line_to(self.to_global_point(*p));
219 }
220 let bounds = self.ctx.stroke(&path, &style, color);
221 self.to_local_bounds(bounds)
222 }
223
224 pub fn curve(
225 &mut self,
226 p0: Point,
227 c0: Point,
228 c1: Point,
229 p1: Point,
230 stroke: BrushStroke,
231 color: Color,
232 ) -> Rect {
233 let span = (p1.x - p0.x).abs() + (p1.y - p0.y).abs();
234 let n = ((span / 12.0) as usize).clamp(4, 32);
235 let mut pts = Vec::with_capacity(n + 1);
236 for i in 0..=n {
237 let t = i as f32 / n as f32;
238 let u = 1.0 - t;
239 pts.push(Point::new(
240 u * u * u * p0.x
241 + 3.0 * u * u * t * c0.x
242 + 3.0 * u * t * t * c1.x
243 + t * t * t * p1.x,
244 u * u * u * p0.y
245 + 3.0 * u * u * t * c0.y
246 + 3.0 * u * t * t * c1.y
247 + t * t * t * p1.y,
248 ));
249 }
250 self.polyline(&pts, stroke, color)
251 }
252
253 pub fn rect_outline(
254 &mut self,
255 rect: Rect,
256 radius: f32,
257 stroke: BrushStroke,
258 color: Color,
259 ) -> Rect {
260 if rect.is_empty() || !stroke.is_visible() || color.a <= 0.0 {
261 return Rect::ZERO;
262 }
263 use sequoia_draw::Path;
264 let style = self.stroke_style(stroke);
265 let mut path = Path::new();
266 let global = self.to_global_rect(rect);
267 let radius = radius * self.unit_scale();
268 if radius > 0.0 {
269 path.rounded_rect(global, radius);
270 } else {
271 path.rect(global);
272 }
273 let bounds = self.ctx.stroke(&path, &style, color);
274 self.to_local_bounds(bounds)
275 }
276
277 pub fn polygon(&mut self, points: &[Point], color: Color) -> Rect {
278 if points.len() < 3 || color.a <= 0.0 {
279 return Rect::ZERO;
280 }
281 let global: Vec<Point> = points.iter().map(|p| self.to_global_point(*p)).collect();
282 let bounds = self.ctx.polygon(&global, color);
283 self.to_local_bounds(bounds)
284 }
285
286 pub fn ellipse(&mut self, center: Point, rx: f32, ry: f32, color: Color) -> Rect {
287 if !(rx > 0.0) || !(ry > 0.0) || color.a <= 0.0 {
288 return Rect::ZERO;
289 }
290 let kx = self.transform.apply_vec(Vec2::new(1.0, 0.0)).length();
291 let ky = self.transform.apply_vec(Vec2::new(0.0, 1.0)).length();
292 let bounds = self
293 .ctx
294 .ellipse(self.to_global_point(center), rx * kx, ry * ky, color);
295 self.to_local_bounds(bounds)
296 }
297
298 pub fn text(&mut self, text: &str, baseline: Point, size: f32, color: Color) {
299 if text.is_empty() || !(size > 0.0) || color.a <= 0.0 {
300 return;
301 }
302 let g = self.to_global_point(baseline);
303 self.ctx.draw_text(text, (g.x, g.y), size, color);
304 }
305
306 pub fn text_tl(&mut self, text: &str, top_left: Point, size: f32, color: Color) {
307 let baseline = Point::new(top_left.x, PaintCtx::baseline_top(top_left.y, size));
308 self.text(text, baseline, size, color);
309 }
310
311 pub fn text_tl_rotated(
312 &mut self,
313 text: &str,
314 top_left: Point,
315 size: f32,
316 color: Color,
317 degrees: f32,
318 pivot: Point,
319 ) {
320 let pivot = self.to_global_point(pivot);
321 let t = Transform::rotate_around_degrees(degrees, pivot);
322 self.push_list_transform(t);
323 self.text_tl(text, top_left, size, color);
324 self.pop_list_transform();
325 }
326
327 pub fn text_styled(
328 &mut self,
329 text: &str,
330 baseline: Point,
331 size: f32,
332 weight: FontWeight,
333 color: Color,
334 ) {
335 if text.is_empty() || !(size > 0.0) || color.a <= 0.0 {
336 return;
337 }
338 let g = self.to_global_point(baseline);
339 self.ctx
340 .draw_text_weighted(text, (g.x, g.y), size, weight, color);
341 }
342
343 pub fn text_width(&mut self, text: &str, size: f32) -> f32 {
344 if text.is_empty() || !(size > 0.0) {
345 return 0.0;
346 }
347 self.ctx.text_width(text, size)
348 }
349
350 pub fn text_width_weighted(&mut self, text: &str, size: f32, weight: FontWeight) -> f32 {
351 if text.is_empty() || !(size > 0.0) {
352 return 0.0;
353 }
354 self.ctx.text_width_weighted(text, size, weight)
355 }
356
357 pub fn text_center_x(&mut self, text: &str, center_x: f32, top: f32, size: f32, color: Color) {
358 let width = self.text_width(text, size);
359 self.text_tl(text, Point::new(center_x - width * 0.5, top), size, color);
360 }
361
362 pub fn text_center(&mut self, rect: Rect, text: &str, size: f32, color: Color) {
363 let metrics = self.ctx.text_metrics(text, size);
364 let top = rect.top() + (rect.size.height - metrics.height) * 0.5;
365 self.text_tl(
366 text,
367 Point::new(rect.center().x - metrics.width * 0.5, top),
368 size,
369 color,
370 );
371 }
372
373 pub fn image(&mut self, rect: Rect, uv: [f32; 4], source: ImageSource) -> Rect {
374 if rect.is_empty() {
375 return Rect::ZERO;
376 }
377 let global = self.to_global_rect(rect);
378 self.ctx.list.push_image(global, uv, source);
379 rect
380 }
381
382 pub fn pixels(&mut self, rect: Rect, pixels: &Pixels) -> Rect {
383 let full = Rect::from_xywh(0.0, 0.0, pixels.width() as f32, pixels.height() as f32);
384 self.pixels_region_sampled(rect, pixels, full, ImageSampling::Linear)
385 }
386
387 pub fn pixels_nearest(&mut self, rect: Rect, pixels: &Pixels) -> Rect {
388 let full = Rect::from_xywh(0.0, 0.0, pixels.width() as f32, pixels.height() as f32);
389 self.pixels_region_sampled(rect, pixels, full, ImageSampling::Nearest)
390 }
391
392 pub fn pixels_region(&mut self, dst: Rect, pixels: &Pixels, src: Rect) -> Rect {
393 self.pixels_region_sampled(dst, pixels, src, ImageSampling::Linear)
394 }
395
396 fn pixels_region_sampled(
397 &mut self,
398 dst: Rect,
399 pixels: &Pixels,
400 src: Rect,
401 sampling: ImageSampling,
402 ) -> Rect {
403 if dst.is_empty() {
404 return Rect::ZERO;
405 }
406 let Some(uv) = Self::pixel_uv(pixels.width(), pixels.height(), src) else {
407 return Rect::ZERO;
408 };
409 let global = self.to_global_rect(dst);
410 self.ctx
411 .list
412 .push_image_sampled(global, uv, pixels.to_source(), sampling);
413 dst
414 }
415
416 pub fn pixel_uv(width: u32, height: u32, src: Rect) -> Option<[f32; 4]> {
417 if width == 0 || height == 0 {
418 return None;
419 }
420 let (w, h) = (width as f32, height as f32);
421 let x0 = src.left().clamp(0.0, w);
422 let y0 = src.top().clamp(0.0, h);
423 let x1 = src.right().clamp(0.0, w);
424 let y1 = src.bottom().clamp(0.0, h);
425 if x1 <= x0 || y1 <= y0 {
426 return None;
427 }
428 Some([x0 / w, y0 / h, x1 / w, y1 / h])
429 }
430
431 pub fn pixel_at(rect: Rect, width: u32, height: u32, p: Point) -> Option<(i32, i32)> {
432 if width == 0 || height == 0 || rect.is_empty() {
433 return None;
434 }
435 let fx = (p.x - rect.left()) / rect.size.width * width as f32;
436 let fy = (p.y - rect.top()) / rect.size.height * height as f32;
437 if !fx.is_finite() || !fy.is_finite() || fx < 0.0 || fy < 0.0 {
438 return None;
439 }
440 if fx >= width as f32 || fy >= height as f32 {
441 return None;
442 }
443 Some((fx.floor() as i32, fy.floor() as i32))
444 }
445
446 pub fn knob(&mut self, center: Point, radius: f32, fill: Color) -> Rect {
447 if !(radius > 0.0) || !radius.is_finite() {
448 return Rect::ZERO;
449 }
450 let outer = self.circle(center, radius, Fill::solid(Color::WHITE));
451 let inner_r = radius - 2.0;
452 if inner_r > 0.0 {
453 let inner = self.circle(center, inner_r, Fill::solid(fill));
454 outer.union(inner)
455 } else {
456 outer
457 }
458 }
459
460 pub fn sv_field(&mut self, rect: Rect, hue01: f32) -> Rect {
461 if rect.is_empty() {
462 return Rect::ZERO;
463 }
464 let h = hue01.clamp(0.0, 1.0);
465 self.fill(rect, Fill::solid(Color::from_hsva(h, 1.0, 1.0, 1.0)));
466 self.fill(
467 rect,
468 Fill::horizontal(GradientStops::two(
469 Color::WHITE,
470 Color::rgba(1.0, 1.0, 1.0, 0.0),
471 )),
472 );
473 self.fill(
474 rect,
475 Fill::vertical(GradientStops::two(
476 Color::rgba(0.0, 0.0, 0.0, 0.0),
477 Color::BLACK,
478 )),
479 );
480 rect
481 }
482
483 pub fn grid(&mut self, step: f32, color: Color) -> u32 {
484 self.grid_offset(step, Vec2::ZERO, color)
485 }
486
487 pub fn grid_offset(&mut self, step: f32, offset: Vec2, color: Color) -> u32 {
488 if step <= 0.0 || color.a <= 0.0 || self.size.width <= 0.0 || self.size.height <= 0.0 {
489 return 0;
490 }
491 let mut drawn = 0u32;
492 let ox = offset.x.rem_euclid(step);
493 let oy = offset.y.rem_euclid(step);
494 let w = self.size.width;
495 let h = self.size.height;
496 let mut x = ox;
497 while x < w {
498 self.fill(Rect::from_xywh(x, 0.0, 1.0, h), Fill::solid(color));
499 drawn += 1;
500 if drawn > 4096 {
501 break;
502 }
503 x += step;
504 }
505 let mut y = oy;
506 while y < h {
507 self.fill(Rect::from_xywh(0.0, y, w, 1.0), Fill::solid(color));
508 drawn += 1;
509 if drawn > 8192 {
510 break;
511 }
512 y += step;
513 }
514 drawn
515 }
516
517 pub fn axes(&mut self, origin: Point, color: Color, width: f32) -> Rect {
518 if !(width > 0.0) || color.a <= 0.0 {
519 return Rect::ZERO;
520 }
521 let stroke = BrushStroke::new(width);
522 let w = self.size.width;
523 let h = self.size.height;
524 let a = self.line(
525 Point::new(0.0, origin.y),
526 Point::new(w, origin.y),
527 stroke,
528 color,
529 );
530 let b = self.line(
531 Point::new(origin.x, 0.0),
532 Point::new(origin.x, h),
533 stroke,
534 color,
535 );
536 a.union(b)
537 }
538
539 pub fn frame(&mut self, rect: Rect, color: Color, width: f32) -> Rect {
540 self.rect_outline(rect, 0.0, BrushStroke::new(width), color)
541 }
542
543 pub fn cross(&mut self, center: Point, half: f32, width: f32, color: Color) -> Rect {
544 if !(half > 0.0) || color.a <= 0.0 {
545 return Rect::ZERO;
546 }
547 let s = BrushStroke::round(width);
548 let a = self.line(
549 Point::new(center.x - half, center.y),
550 Point::new(center.x + half, center.y),
551 s,
552 color,
553 );
554 let b = self.line(
555 Point::new(center.x, center.y - half),
556 Point::new(center.x, center.y + half),
557 s,
558 color,
559 );
560 a.union(b)
561 }
562
563 pub fn marquee(&mut self, a: Point, b: Point, fill: Fill, outline: Color) -> Rect {
564 let rect = Rect::from_xywh(
565 a.x.min(b.x),
566 a.y.min(b.y),
567 (a.x - b.x).abs(),
568 (a.y - b.y).abs(),
569 );
570 if rect.is_empty() {
571 return Rect::ZERO;
572 }
573 self.fill(rect, fill);
574 self.rect_outline(rect, 0.0, BrushStroke::new(1.0), outline);
575 rect
576 }
577
578 pub fn ellipse_outline(
579 &mut self,
580 center: Point,
581 rx: f32,
582 ry: f32,
583 stroke: BrushStroke,
584 color: Color,
585 ) -> Rect {
586 if !(rx > 0.0) || !(ry > 0.0) || !stroke.is_visible() || color.a <= 0.0 {
587 return Rect::ZERO;
588 }
589 use sequoia_draw::Path;
590 let style = self.stroke_style(stroke);
591 let kx = self.transform.apply_vec(Vec2::new(1.0, 0.0)).length();
592 let ky = self.transform.apply_vec(Vec2::new(0.0, 1.0)).length();
593 let mut path = Path::new();
594 path.ellipse(self.to_global_point(center), rx * kx, ry * ky);
595 let bounds = self.ctx.stroke(&path, &style, color);
596 self.to_local_bounds(bounds)
597 }
598
599 pub fn capsule(&mut self, from: Point, to: Point, radius: f32, color: Color) -> Rect {
600 if !(radius > 0.0) || color.a <= 0.0 {
601 return Rect::ZERO;
602 }
603 let bounds = self.ctx.capsule(
604 self.to_global_point(from),
605 self.to_global_point(to),
606 radius,
607 color,
608 );
609 self.to_local_bounds(bounds)
610 }
611
612 pub fn arrow(
613 &mut self,
614 from: Point,
615 to: Point,
616 width: f32,
617 head_len: f32,
618 head_width: f32,
619 color: Color,
620 ) -> Rect {
621 if color.a <= 0.0 {
622 return Rect::ZERO;
623 }
624 let bounds = self.ctx.arrow(
625 self.to_global_point(from),
626 self.to_global_point(to),
627 width,
628 head_len,
629 head_width,
630 color,
631 );
632 self.to_local_bounds(bounds)
633 }
634
635 pub fn x_marker(&mut self, center: Point, half: f32, width: f32, color: Color) -> Rect {
636 if !(half > 0.0) || color.a <= 0.0 {
637 return Rect::ZERO;
638 }
639 let bounds = self
640 .ctx
641 .x_marker(self.to_global_point(center), half, width, color);
642 self.to_local_bounds(bounds)
643 }
644
645 pub fn regular_polygon(
646 &mut self,
647 center: Point,
648 radius: f32,
649 sides: u32,
650 rotation_deg: f32,
651 color: Color,
652 ) -> Rect {
653 if !(radius > 0.0) || sides < 3 || color.a <= 0.0 {
654 return Rect::ZERO;
655 }
656 let bounds = self.ctx.regular_polygon(
657 self.to_global_point(center),
658 radius,
659 sides,
660 rotation_deg,
661 color,
662 );
663 self.to_local_bounds(bounds)
664 }
665
666 pub fn star(
667 &mut self,
668 center: Point,
669 outer_r: f32,
670 inner_r: f32,
671 points: u32,
672 rotation_deg: f32,
673 color: Color,
674 ) -> Rect {
675 if !(outer_r > 0.0) || !(inner_r > 0.0) || points < 2 || color.a <= 0.0 {
676 return Rect::ZERO;
677 }
678 let bounds = self.ctx.star(
679 self.to_global_point(center),
680 outer_r,
681 inner_r,
682 points,
683 rotation_deg,
684 color,
685 );
686 self.to_local_bounds(bounds)
687 }
688
689 pub fn dots(&mut self, step: f32, offset: Vec2, radius: f32, color: Color) -> u32 {
690 if step <= 0.0 || !(radius > 0.0) || color.a <= 0.0 {
691 return 0;
692 }
693 if self.size.width <= 0.0 || self.size.height <= 0.0 {
694 return 0;
695 }
696 let mut n = 0u32;
697 let ox = offset.x.rem_euclid(step);
698 let oy = offset.y.rem_euclid(step);
699 let mut y = oy;
700 while y < self.size.height {
701 let mut x = ox;
702 while x < self.size.width {
703 self.circle(Point::new(x, y), radius, Fill::solid(color));
704 n += 1;
705 if n > 16384 {
706 return n;
707 }
708 x += step;
709 }
710 y += step;
711 }
712 n
713 }
714
715 pub fn grid_split(
716 &mut self,
717 step: f32,
718 every: u32,
719 offset: Vec2,
720 minor: Color,
721 major: Color,
722 ) -> u32 {
723 if step <= 0.0 || every == 0 {
724 return 0;
725 }
726 if self.size.width <= 0.0 || self.size.height <= 0.0 {
727 return 0;
728 }
729 let every = every.max(1);
730 let mut drawn = 0u32;
731 let ox = offset.x.rem_euclid(step);
732 let oy = offset.y.rem_euclid(step);
733 let w = self.size.width;
734 let h = self.size.height;
735 let mut i = 0u32;
736 let mut x = ox;
737 while x < w {
738 let c = if i % every == every - 1 { major } else { minor };
739 self.fill(Rect::from_xywh(x, 0.0, 1.0, h), Fill::solid(c));
740 drawn += 1;
741 if drawn > 4096 {
742 break;
743 }
744 x += step;
745 i += 1;
746 }
747 let mut j = 0u32;
748 let mut y = oy;
749 while y < h {
750 let c = if j % every == every - 1 { major } else { minor };
751 self.fill(Rect::from_xywh(0.0, y, w, 1.0), Fill::solid(c));
752 drawn += 1;
753 if drawn > 8192 {
754 break;
755 }
756 y += step;
757 j += 1;
758 }
759 drawn
760 }
761
762 pub fn clipped<R>(&mut self, rect: Rect, f: impl FnOnce(&mut Self) -> R) -> R {
763 let global = self.to_global_rect(rect);
764 self.ctx.list.push_clip(global);
765 let r = f(self);
766 self.ctx.list.pop_clip();
767 r
768 }
769
770 pub fn clipped_rounded<R>(
771 &mut self,
772 rect: Rect,
773 radius: f32,
774 f: impl FnOnce(&mut Self) -> R,
775 ) -> R {
776 let global = self.to_global_rect(rect);
777 self.ctx.push_rounded_clip(global, radius);
778 let r = f(self);
779 self.ctx.pop_clip();
780 r
781 }
782
783 fn push_gradient(
784 &mut self,
785 rect: Rect,
786 radius: f32,
787 kind: GradientKind,
788 stops: GradientStops,
789 ) -> Rect {
790 if stops.len() <= sequoia_core::MAX_GRADIENT_STOPS {
791 let global = self.to_global_rect(rect);
792 self.ctx.list.push_gradient(global, radius, kind, stops);
793 return rect;
794 }
795
796 self.push_gradient_bands(rect, radius, kind, stops)
797 }
798
799 fn push_gradient_bands(
800 &mut self,
801 rect: Rect,
802 shape_radius: f32,
803 kind: GradientKind,
804 stops: GradientStops,
805 ) -> Rect {
806 const RADIAL_BANDS: usize = 64;
807 let vertical = match kind {
808 GradientKind::Linear { angle_deg } => {
809 let (dx, dy) = GradientKind::Linear { angle_deg }.direction();
810 dy.abs() >= dx.abs()
811 }
812 GradientKind::Radial { .. } => true,
813 };
814 let global = self.to_global_rect(rect);
815 let mut bounds = Rect::ZERO;
816 match kind {
817 GradientKind::Radial { radius } => {
818 let center = Point::new(
819 global.left() + global.size.width * 0.5,
820 global.top() + global.size.height * 0.5,
821 );
822 let extent = if radius > 0.0 {
823 radius
824 } else {
825 global.size.width.hypot(global.size.height) * 0.5
826 };
827 if extent <= 0.0 {
828 return Rect::ZERO;
829 }
830 if shape_radius > 0.0 {
831 self.ctx.push_rounded_clip(global, shape_radius);
832 } else {
833 self.ctx.list.push_clip(global);
834 }
835 for i in (0..RADIAL_BANDS).rev() {
836 let t0 = i as f32 / RADIAL_BANDS as f32;
837 let t1 = (i + 1) as f32 / RADIAL_BANDS as f32;
838 let [r, g, b, a] = stops.sample_linear((t0 + t1) * 0.5);
839 let color = Color { r, g, b, a }.from_linear();
840 let r1 = extent * t1;
841 let band = Rect::from_xywh(center.x - r1, center.y - r1, r1 * 2.0, r1 * 2.0);
842 self.ctx.list.push_quad(band, color, r1);
843 }
844 self.ctx.pop_clip();
845 bounds = global;
846 }
847 GradientKind::Linear { .. } => {
848 let extent = if vertical {
849 global.size.height
850 } else {
851 global.size.width
852 };
853 let bands = (extent.ceil() as usize).clamp(1, 256);
854 let scale = {
855 let s = self.ctx.scale_factor();
856 if s.is_finite() && s > 0.0 { s } else { 1.0 }
857 };
858 let origin = if vertical {
859 global.top()
860 } else {
861 global.left()
862 };
863 let edges =
864 crate::paint_draw::band_edges(origin, extent, bands, 1.0 / scale, 0.5 / scale);
865 for (i, (lo, hi)) in edges.into_iter().enumerate() {
866 let t = (i as f32 + 0.5) / bands as f32;
867 let [r, g, b, a] = stops.sample_linear(t);
868 let color = Color { r, g, b, a }.from_linear();
869 let band = if vertical {
870 Rect::from_xywh(global.left(), lo, global.size.width, hi - lo)
871 } else {
872 Rect::from_xywh(lo, global.top(), hi - lo, global.size.height)
873 };
874 self.ctx.list.push_quad(band, color, 0.0);
875 bounds = bounds.union(band);
876 }
877 }
878 }
879 if bounds.is_empty() {
880 return Rect::ZERO;
881 }
882
883 Rect::from_xywh(
884 bounds.left() - self.origin.x,
885 bounds.top() - self.origin.y,
886 bounds.size.width,
887 bounds.size.height,
888 )
889 }
890
891 fn to_local_bounds(&self, bounds: Rect) -> Rect {
892 if bounds.is_empty() {
893 return Rect::ZERO;
894 }
895 let r = Rect::from_xywh(
896 bounds.left() - self.origin.x,
897 bounds.top() - self.origin.y,
898 bounds.size.width,
899 bounds.size.height,
900 );
901
902 match self.transform.inverse() {
903 Some(inv) => inv.apply_rect_aabb(r),
904 None => Rect::ZERO,
905 }
906 }
907}