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