1use oxiui_core::geometry::Rect;
17use oxiui_core::paint::{DrawCommand, DrawList, GradientStop, ImageFilter};
18use oxiui_core::Color;
19
20use crate::clip::{ClipRect, ClipStack};
21use crate::gpu::buffer::{
22 push_circle_quad, push_ellipse_quad, push_gradient_quad, push_line_quad, push_nine_slice_quads,
23 push_rect_quad, push_rounded_rect_per_corner_quad, push_rounded_rect_quad, push_textured_quad,
24 GradientUniforms, GradientVertex, LineQuadParams, TexQuadParams, Vertex, MAX_GRADIENT_STOPS,
25};
26use crate::gpu::tessellator::{tessellate_fill, tessellate_stroke};
27use crate::gpu::texture::TexturedDraw;
28
29#[derive(Clone, Copy, Debug)]
33pub(crate) struct DrawSegment {
34 pub(crate) start: u32,
35 pub(crate) end: u32,
36 pub(crate) scissor: Option<[u32; 4]>,
37}
38
39pub(crate) struct GradientDraw {
43 pub(crate) verts: Vec<GradientVertex>,
44 pub(crate) uniforms: GradientUniforms,
45 pub(crate) scissor: Option<[u32; 4]>,
46}
47
48#[allow(dead_code)] pub(crate) struct BackdropBlurDraw {
56 pub(crate) rect: [f32; 4],
58 pub(crate) blur_radius: f32,
60}
61
62pub(crate) fn scissor_from_stack(
66 stack: &ClipStack,
67 viewport_w: u32,
68 viewport_h: u32,
69) -> Option<[u32; 4]> {
70 let raw = stack.as_scissor()?;
71 Some(clamp_scissor(raw, viewport_w, viewport_h))
72}
73
74pub(crate) fn clamp_scissor([x, y, w, h]: [u32; 4], viewport_w: u32, viewport_h: u32) -> [u32; 4] {
76 let x = x.min(viewport_w);
77 let y = y.min(viewport_h);
78 let w = w.min(viewport_w - x);
79 let h = h.min(viewport_h - y);
80 [x, y, w, h]
81}
82
83pub(crate) type GeometryOutput = (
87 Vec<Vertex>,
88 Vec<DrawSegment>,
89 Vec<GradientDraw>,
90 Vec<TexturedDraw>,
91 Vec<BackdropBlurDraw>,
92);
93
94pub(crate) fn build_geometry(list: &DrawList, viewport_w: u32, viewport_h: u32) -> GeometryOutput {
99 let mut verts: Vec<Vertex> = Vec::new();
100 let mut segments: Vec<DrawSegment> = Vec::new();
101 let mut gradient_draws: Vec<GradientDraw> = Vec::new();
102 let mut textured_draws: Vec<TexturedDraw> = Vec::new();
103 let mut backdrop_blur_draws: Vec<BackdropBlurDraw> = Vec::new();
104 let mut stack = ClipStack::new();
105
106 let mut current_scissor = scissor_from_stack(&stack, viewport_w, viewport_h);
107 let mut segment_start: u32 = 0;
108
109 let flush = |segs: &mut Vec<DrawSegment>, start: u32, end: u32, sc: Option<[u32; 4]>| {
110 if end > start {
111 segs.push(DrawSegment {
112 start,
113 end,
114 scissor: sc,
115 });
116 }
117 };
118
119 for cmd in list.iter() {
120 match cmd {
122 DrawCommand::PushClip { rect } => {
123 flush(
124 &mut segments,
125 segment_start,
126 verts.len() as u32,
127 current_scissor,
128 );
129 stack.push(ClipRect::new(
130 rect.left(),
131 rect.top(),
132 rect.width(),
133 rect.height(),
134 ));
135 current_scissor = scissor_from_stack(&stack, viewport_w, viewport_h);
136 segment_start = verts.len() as u32;
137 continue;
138 }
139 DrawCommand::PopClip => {
140 flush(
141 &mut segments,
142 segment_start,
143 verts.len() as u32,
144 current_scissor,
145 );
146 stack.pop();
147 current_scissor = scissor_from_stack(&stack, viewport_w, viewport_h);
148 segment_start = verts.len() as u32;
149 continue;
150 }
151 _ => {}
152 }
153
154 if let Some(scissor) = current_scissor {
160 if scissor[2] == 0 || scissor[3] == 0 {
162 continue;
163 }
164 if let Some(bounds) = cmd_bounds(cmd) {
165 let scissor_rect = scissor_to_rect(scissor);
166 if !rects_intersect(&bounds, &scissor_rect) {
167 continue;
168 }
169 }
170 }
171
172 match cmd {
174 DrawCommand::PushClip { .. } | DrawCommand::PopClip => {}
176
177 DrawCommand::FillRect { rect, color } => {
178 push_rect_quad(
179 &mut verts,
180 rect.left(),
181 rect.top(),
182 rect.width(),
183 rect.height(),
184 *color,
185 );
186 }
187 DrawCommand::StrokeRect {
188 rect,
189 thickness,
190 color,
191 } => {
192 emit_stroke_rect(
193 &mut verts,
194 rect.left(),
195 rect.top(),
196 rect.width(),
197 rect.height(),
198 *thickness,
199 *color,
200 );
201 }
202 DrawCommand::FillRoundedRect {
203 rect,
204 radius,
205 color,
206 } => {
207 push_rounded_rect_quad(
208 &mut verts,
209 rect.left(),
210 rect.top(),
211 rect.width(),
212 rect.height(),
213 *radius,
214 *color,
215 );
216 }
217 DrawCommand::FillRoundedRectPerCorner { rect, radii, color } => {
218 push_rounded_rect_per_corner_quad(
219 &mut verts,
220 rect.left(),
221 rect.top(),
222 rect.width(),
223 rect.height(),
224 *radii,
225 *color,
226 );
227 }
228 DrawCommand::FillCircle {
229 center,
230 radius,
231 color,
232 } => {
233 push_circle_quad(&mut verts, center.x, center.y, *radius, *color);
234 }
235 DrawCommand::FillEllipse {
236 center,
237 rx,
238 ry,
239 color,
240 } => {
241 push_ellipse_quad(&mut verts, center.x, center.y, *rx, *ry, *color);
242 }
243 DrawCommand::Line { from, to, color } => {
244 push_line_quad(
245 &mut verts,
246 LineQuadParams {
247 from_x: from.x,
248 from_y: from.y,
249 to_x: to.x,
250 to_y: to.y,
251 half_width: 0.5,
252 color: *color,
253 aa_smooth: false,
254 },
255 );
256 }
257 DrawCommand::LineAa { from, to, color } => {
258 push_line_quad(
259 &mut verts,
260 LineQuadParams {
261 from_x: from.x,
262 from_y: from.y,
263 to_x: to.x,
264 to_y: to.y,
265 half_width: 0.5,
266 color: *color,
267 aa_smooth: true,
268 },
269 );
270 }
271 DrawCommand::LineThick {
272 from,
273 to,
274 width,
275 color,
276 } => {
277 push_line_quad(
278 &mut verts,
279 LineQuadParams {
280 from_x: from.x,
281 from_y: from.y,
282 to_x: to.x,
283 to_y: to.y,
284 half_width: width * 0.5,
285 color: *color,
286 aa_smooth: true,
287 },
288 );
289 }
290 DrawCommand::LineDashed {
291 from,
292 to,
293 dash_len,
294 gap_len,
295 color,
296 } => {
297 emit_dashed_line(
298 &mut verts,
299 DashedLineParams {
300 x0: from.x,
301 y0: from.y,
302 x1: to.x,
303 y1: to.y,
304 dash_len: *dash_len,
305 gap_len: *gap_len,
306 color: *color,
307 },
308 );
309 }
310 DrawCommand::FillPath { path, color } => {
311 tessellate_fill(&mut verts, path, *color);
312 }
313 DrawCommand::StrokePath { path, style, color } => {
314 tessellate_stroke(&mut verts, path, style, *color);
315 }
316 DrawCommand::LinearGradient {
317 rect,
318 start,
319 end,
320 stops,
321 } => {
322 if let Some(gd) = build_gradient_draw_linear(LinearGradientParams {
323 x: rect.left(),
324 y: rect.top(),
325 w: rect.width(),
326 h: rect.height(),
327 sx: start.x,
328 sy: start.y,
329 ex: end.x,
330 ey: end.y,
331 stops,
332 scissor: current_scissor,
333 }) {
334 gradient_draws.push(gd);
335 }
336 }
337 DrawCommand::RadialGradient {
338 rect,
339 center,
340 radius,
341 stops,
342 } => {
343 if let Some(gd) = build_gradient_draw_radial(RadialGradientParams {
344 x: rect.left(),
345 y: rect.top(),
346 w: rect.width(),
347 h: rect.height(),
348 cx: center.x,
349 cy: center.y,
350 radius: *radius,
351 stops,
352 scissor: current_scissor,
353 }) {
354 gradient_draws.push(gd);
355 }
356 }
357 DrawCommand::Image {
358 image,
359 dest,
360 filter,
361 } => {
362 let mut tex_verts = Vec::new();
363 push_textured_quad(
364 &mut tex_verts,
365 TexQuadParams {
366 x: dest.left(),
367 y: dest.top(),
368 w: dest.width(),
369 h: dest.height(),
370 u0: 0.0,
371 v0: 0.0,
372 u1: 1.0,
373 v1: 1.0,
374 tint: [1.0, 1.0, 1.0, 1.0],
375 },
376 );
377 textured_draws.push(TexturedDraw {
378 verts: tex_verts,
379 image: image.clone(),
380 filter: *filter,
381 scissor: current_scissor,
382 });
383 }
384 DrawCommand::NineSlice {
385 image,
386 dest,
387 insets,
388 } => {
389 let mut tex_verts = Vec::new();
390 push_nine_slice_quads(
391 &mut tex_verts,
392 [dest.left(), dest.top(), dest.width(), dest.height()],
393 image.width,
394 image.height,
395 *insets,
396 [1.0, 1.0, 1.0, 1.0],
397 );
398 textured_draws.push(TexturedDraw {
399 verts: tex_verts,
400 image: image.clone(),
401 filter: ImageFilter::Nearest,
402 scissor: current_scissor,
403 });
404 }
405 DrawCommand::BackdropBlur { rect, blur_radius } => {
406 backdrop_blur_draws.push(BackdropBlurDraw {
410 rect: [rect.left(), rect.top(), rect.width(), rect.height()],
411 blur_radius: *blur_radius,
412 });
413 }
414 _ => {}
419 }
420 }
421
422 flush(
423 &mut segments,
424 segment_start,
425 verts.len() as u32,
426 current_scissor,
427 );
428 (
429 verts,
430 segments,
431 gradient_draws,
432 textured_draws,
433 backdrop_blur_draws,
434 )
435}
436
437pub(crate) fn cmd_bounds(cmd: &DrawCommand) -> Option<Rect> {
446 match cmd {
447 DrawCommand::FillRect { rect, .. }
448 | DrawCommand::StrokeRect { rect, .. }
449 | DrawCommand::FillRoundedRect { rect, .. }
450 | DrawCommand::FillRoundedRectPerCorner { rect, .. }
451 | DrawCommand::LinearGradient { rect, .. }
452 | DrawCommand::RadialGradient { rect, .. }
453 | DrawCommand::Image { dest: rect, .. }
454 | DrawCommand::NineSlice { dest: rect, .. }
455 | DrawCommand::DrawText { rect, .. } => Some(*rect),
456
457 DrawCommand::FillCircle { center, radius, .. } => Some(Rect::new(
458 center.x - radius,
459 center.y - radius,
460 radius * 2.0,
461 radius * 2.0,
462 )),
463
464 DrawCommand::FillEllipse { center, rx, ry, .. } => {
465 Some(Rect::new(center.x - rx, center.y - ry, rx * 2.0, ry * 2.0))
466 }
467
468 DrawCommand::Line { from, to, .. } | DrawCommand::LineAa { from, to, .. } => {
469 Some(rect_from_points(*from, *to))
470 }
471
472 DrawCommand::LineThick {
473 from, to, width, ..
474 } => {
475 let r = rect_from_points(*from, *to);
476 Some(Rect::new(
477 r.left() - width,
478 r.top() - width,
479 r.width() + width * 2.0,
480 r.height() + width * 2.0,
481 ))
482 }
483
484 DrawCommand::LineDashed { from, to, .. } => Some(rect_from_points(*from, *to)),
485
486 DrawCommand::FillPath { path, .. } => path.bounds(),
487
488 DrawCommand::StrokePath { path, style, .. } => path.bounds().map(|b| {
489 let pad = style.width / 2.0;
490 Rect::new(
491 b.left() - pad,
492 b.top() - pad,
493 b.width() + style.width,
494 b.height() + style.width,
495 )
496 }),
497
498 DrawCommand::PushClip { .. } | DrawCommand::PopClip => None,
500
501 DrawCommand::BoxShadow { .. } => None,
505
506 _ => None,
508 }
509}
510
511pub(crate) fn rect_from_points(
514 a: oxiui_core::geometry::Point,
515 b: oxiui_core::geometry::Point,
516) -> Rect {
517 let x = a.x.min(b.x);
518 let y = a.y.min(b.y);
519 let w = (a.x - b.x).abs().max(1.0);
520 let h = (a.y - b.y).abs().max(1.0);
521 Rect::new(x, y, w, h)
522}
523
524pub(crate) fn rects_intersect(a: &Rect, b: &Rect) -> bool {
528 a.left() < b.left() + b.width()
529 && a.left() + a.width() > b.left()
530 && a.top() < b.top() + b.height()
531 && a.top() + a.height() > b.top()
532}
533
534pub(crate) fn scissor_to_rect(s: [u32; 4]) -> Rect {
537 Rect::new(s[0] as f32, s[1] as f32, s[2] as f32, s[3] as f32)
538}
539
540pub(crate) fn emit_stroke_rect(
543 out: &mut Vec<Vertex>,
544 x: f32,
545 y: f32,
546 w: f32,
547 h: f32,
548 t: f32,
549 color: Color,
550) {
551 push_rect_quad(out, x, y, w, t, color);
552 push_rect_quad(out, x, y + h - t, w, t, color);
553 push_rect_quad(out, x, y + t, t, h - 2.0 * t, color);
554 push_rect_quad(out, x + w - t, y + t, t, h - 2.0 * t, color);
555}
556
557pub(crate) struct DashedLineParams {
558 pub(crate) x0: f32,
559 pub(crate) y0: f32,
560 pub(crate) x1: f32,
561 pub(crate) y1: f32,
562 pub(crate) dash_len: f32,
563 pub(crate) gap_len: f32,
564 pub(crate) color: Color,
565}
566
567pub(crate) fn emit_dashed_line(out: &mut Vec<Vertex>, p: DashedLineParams) {
568 let DashedLineParams {
569 x0,
570 y0,
571 x1,
572 y1,
573 dash_len,
574 gap_len,
575 color,
576 } = p;
577 let dx = x1 - x0;
578 let dy = y1 - y0;
579 let total = (dx * dx + dy * dy).sqrt();
580 if total < 1e-6 || dash_len <= 0.0 {
581 return;
582 }
583 let ux = dx / total;
584 let uy = dy / total;
585 let period = dash_len + gap_len.max(0.0);
586 if period < 1e-6 {
587 return;
588 }
589 let mut t = 0.0_f32;
590 while t < total {
591 let end = (t + dash_len).min(total);
592 push_line_quad(
593 out,
594 LineQuadParams {
595 from_x: x0 + ux * t,
596 from_y: y0 + uy * t,
597 to_x: x0 + ux * end,
598 to_y: y0 + uy * end,
599 half_width: 0.5,
600 color,
601 aa_smooth: false,
602 },
603 );
604 t += period;
605 }
606}
607
608pub(crate) struct LinearGradientParams<'a> {
609 pub(crate) x: f32,
610 pub(crate) y: f32,
611 pub(crate) w: f32,
612 pub(crate) h: f32,
613 pub(crate) sx: f32,
614 pub(crate) sy: f32,
615 pub(crate) ex: f32,
616 pub(crate) ey: f32,
617 pub(crate) stops: &'a [GradientStop],
618 pub(crate) scissor: Option<[u32; 4]>,
619}
620
621pub(crate) fn build_gradient_draw_linear(p: LinearGradientParams<'_>) -> Option<GradientDraw> {
622 let LinearGradientParams {
623 x,
624 y,
625 w,
626 h,
627 sx,
628 sy,
629 ex,
630 ey,
631 stops,
632 scissor,
633 } = p;
634 let uniforms = build_gradient_uniforms(0, [sx, sy], [ex, ey], 0.0, stops)?;
635 let mut verts = Vec::new();
636 push_gradient_quad(&mut verts, x, y, w, h);
637 Some(GradientDraw {
638 verts,
639 uniforms,
640 scissor,
641 })
642}
643
644pub(crate) struct RadialGradientParams<'a> {
645 pub(crate) x: f32,
646 pub(crate) y: f32,
647 pub(crate) w: f32,
648 pub(crate) h: f32,
649 pub(crate) cx: f32,
650 pub(crate) cy: f32,
651 pub(crate) radius: f32,
652 pub(crate) stops: &'a [GradientStop],
653 pub(crate) scissor: Option<[u32; 4]>,
654}
655
656pub(crate) fn build_gradient_draw_radial(p: RadialGradientParams<'_>) -> Option<GradientDraw> {
657 let RadialGradientParams {
658 x,
659 y,
660 w,
661 h,
662 cx,
663 cy,
664 radius,
665 stops,
666 scissor,
667 } = p;
668 let uniforms = build_gradient_uniforms(1, [cx, cy], [0.0, 0.0], radius, stops)?;
669 let mut verts = Vec::new();
670 push_gradient_quad(&mut verts, x, y, w, h);
671 Some(GradientDraw {
672 verts,
673 uniforms,
674 scissor,
675 })
676}
677
678pub(crate) fn build_gradient_uniforms(
679 gradient_type: u32,
680 p0: [f32; 2],
681 p1: [f32; 2],
682 radius: f32,
683 stops: &[GradientStop],
684) -> Option<GradientUniforms> {
685 if stops.is_empty() {
686 return None;
687 }
688 let count = stops.len().min(MAX_GRADIENT_STOPS);
689 let mut stop_offsets = [[0.0f32; 4]; MAX_GRADIENT_STOPS];
690 let mut stop_colors = [[0.0f32; 4]; MAX_GRADIENT_STOPS];
691 for (i, s) in stops.iter().take(count).enumerate() {
692 stop_offsets[i] = [s.offset, 0.0, 0.0, 0.0];
693 stop_colors[i] = [
694 s.color.0 as f32 / 255.0,
695 s.color.1 as f32 / 255.0,
696 s.color.2 as f32 / 255.0,
697 s.color.3 as f32 / 255.0,
698 ];
699 }
700 Some(GradientUniforms {
701 p0,
702 p1,
703 radius,
704 gradient_type,
705 stop_count: count as u32,
706 _pad: 0,
707 stop_offsets,
708 stop_colors,
709 })
710}