1use super::*;
2
3pub(super) fn multiply_opacity(parent: f32, child: f32) -> f32 {
4 (parent * child).clamp(0.0, 1.0)
5}
6
7pub(super) fn border_widths(border: &Border) -> BorderWidth {
8 if border.widths.is_zero() {
9 BorderWidth::all(border.width)
10 } else {
11 border.widths
12 }
13}
14
15pub(super) fn element_needs_measure(element: &Rect) -> bool {
16 matches!(element.width, Length::Fit) || matches!(element.height, Length::Fit)
17}
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20pub(super) struct PaintOffset {
21 pub(super) x: i32,
22 pub(super) y: i32,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq)]
26pub struct PaintTransform {
27 pub scale: f32,
28 pub translate_x: i32,
29 pub translate_y: i32,
30}
31
32impl PaintTransform {
33 pub const IDENTITY: Self = Self {
34 scale: 1.0,
35 translate_x: 0,
36 translate_y: 0,
37 };
38
39 pub const fn new(scale: f32, translate_x: i32, translate_y: i32) -> Self {
40 Self {
41 scale,
42 translate_x,
43 translate_y,
44 }
45 }
46
47 pub const fn scale(scale: f32) -> Self {
48 Self {
49 scale,
50 ..Self::IDENTITY
51 }
52 }
53
54 pub const fn translate(translate_x: i32, translate_y: i32) -> Self {
55 Self {
56 translate_x,
57 translate_y,
58 ..Self::IDENTITY
59 }
60 }
61
62 pub(super) fn is_identity(self) -> bool {
63 self.scale == 1.0 && self.translate_x == 0 && self.translate_y == 0
64 }
65}
66
67impl Default for PaintTransform {
68 fn default() -> Self {
69 Self::IDENTITY
70 }
71}
72
73pub(super) fn scale_i32(value: i32, scale: u32) -> i32 {
74 let scaled = i64::from(value) * i64::from(scale);
75 scaled.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
76}
77
78pub(super) fn scale_i32_f32(value: i32, scale: f32) -> i32 {
79 let scaled = f64::from(value) * f64::from(scale);
80 if !scaled.is_finite() {
81 return if scaled.is_sign_negative() {
82 i32::MIN
83 } else {
84 i32::MAX
85 };
86 }
87 scaled
88 .round()
89 .clamp(f64::from(i32::MIN), f64::from(i32::MAX)) as i32
90}
91
92fn paint_command_with_offset(
93 canvas: &mut Canvas<'_>,
94 fonts: &mut FontCtx,
95 command: PaintCommand<'_>,
96 offset: PaintOffset,
97) {
98 match command {
99 PaintCommand::Rect {
100 rect,
101 clip,
102 opacity,
103 paint,
104 gradient,
105 radii,
106 } => fill_rounded_rect(canvas, rect, clip, opacity, radii, paint, gradient, offset),
107 PaintCommand::Border {
108 rect,
109 clip,
110 opacity,
111 paint,
112 gradient,
113 widths,
114 radii,
115 } => stroke_rounded_rect(
116 canvas, rect, clip, opacity, widths, radii, paint, gradient, offset,
117 ),
118 PaintCommand::Text {
119 rect,
120 clip,
121 opacity,
122 text,
123 } => draw_text(canvas, fonts, rect, clip, opacity, text, offset),
124 PaintCommand::RichText {
125 rect,
126 clip,
127 opacity,
128 text,
129 } => draw_rich_text(canvas, fonts, rect, clip, opacity, text, offset),
130 PaintCommand::Image {
131 rect,
132 clip,
133 opacity,
134 image,
135 } => draw_image(canvas, rect, clip, opacity, image, offset),
136 }
137}
138
139fn normalized_opacity(opacity: f32) -> f32 {
140 opacity.clamp(0.0, 1.0)
141}
142
143pub(super) fn opacity_draws(opacity: f32) -> bool {
144 normalized_opacity(opacity) > 0.0
145}
146
147pub(super) fn color_with_opacity_and_coverage(color: Color, opacity: f32, coverage: u8) -> [u8; 4] {
148 let opacity = normalized_opacity(opacity);
149 if opacity >= 1.0 && coverage == 255 {
150 return color.into();
151 }
152
153 let alpha = (f32::from(color.alpha) * opacity * (f32::from(coverage) / 255.0)).round() as u8;
154 Color { alpha, ..color }.into()
155}
156
157pub(super) fn pixel_with_opacity_and_coverage(
158 mut rgba: [u8; 4],
159 opacity: f32,
160 coverage: u8,
161) -> [u8; 4] {
162 let opacity = normalized_opacity(opacity);
163 if opacity < 1.0 || coverage != 255 {
164 rgba[3] = (f32::from(rgba[3]) * opacity * (f32::from(coverage) / 255.0)).round() as u8;
165 }
166 rgba
167}
168
169fn visible_world_bounds(canvas: &Canvas<'_>, offset: PaintOffset) -> Option<Bounds> {
170 let left = i64::from(offset.x).max(0);
171 let top = i64::from(offset.y).max(0);
172 let right = i64::from(offset.x).saturating_add(i64::from(canvas.width()));
173 let bottom = i64::from(offset.y).saturating_add(i64::from(canvas.height()));
174 if right <= left || bottom <= top {
175 return None;
176 }
177
178 Some(Bounds {
179 x: left.min(i64::from(u32::MAX)) as u32,
180 y: top.min(i64::from(u32::MAX)) as u32,
181 width: right.min(i64::from(u32::MAX)).saturating_sub(left).max(0) as u32,
182 height: bottom.min(i64::from(u32::MAX)).saturating_sub(top).max(0) as u32,
183 })
184}
185
186pub(super) fn visible_draw_bounds(
187 canvas: &Canvas<'_>,
188 bounds: Bounds,
189 offset: PaintOffset,
190) -> Option<Bounds> {
191 bounds.intersect(visible_world_bounds(canvas, offset)?)
192}
193
194pub(super) fn target_coord(world: u32, offset: i32, max: u32) -> Option<u32> {
195 let target = i64::from(world) - i64::from(offset);
196 (target >= 0 && target < i64::from(max)).then_some(target as u32)
197}
198
199pub(super) fn paint_scaled_command_with_offset(
200 canvas: &mut Canvas<'_>,
201 fonts: &mut FontCtx,
202 command: PaintCommand<'_>,
203 scale: u32,
204 offset: PaintOffset,
205) {
206 if scale == 1 {
207 paint_command_with_offset(canvas, fonts, command, offset);
208 return;
209 }
210
211 match command {
212 PaintCommand::Rect {
213 rect,
214 clip,
215 opacity,
216 paint,
217 gradient,
218 radii,
219 } => fill_rounded_rect(
220 canvas,
221 scale_bounds(rect, scale),
222 scale_clip(clip, scale),
223 opacity,
224 radii.scaled(scale),
225 paint,
226 gradient,
227 offset,
228 ),
229 PaintCommand::Border {
230 rect,
231 clip,
232 opacity,
233 paint,
234 gradient,
235 widths,
236 radii,
237 } => stroke_rounded_rect(
238 canvas,
239 scale_bounds(rect, scale),
240 scale_clip(clip, scale),
241 opacity,
242 widths.scaled(scale),
243 radii.scaled(scale),
244 paint,
245 gradient,
246 offset,
247 ),
248 PaintCommand::Text {
249 rect,
250 clip,
251 opacity,
252 text,
253 } => {
254 let text = scaled_text(text, scale);
255 draw_text(
256 canvas,
257 fonts,
258 scale_bounds(rect, scale),
259 scale_clip(clip, scale),
260 opacity,
261 &text,
262 offset,
263 );
264 }
265 PaintCommand::RichText {
266 rect,
267 clip,
268 opacity,
269 text,
270 } => {
271 let text = scaled_rich_text(text, scale);
272 draw_rich_text(
273 canvas,
274 fonts,
275 scale_bounds(rect, scale),
276 scale_clip(clip, scale),
277 opacity,
278 &text,
279 offset,
280 );
281 }
282 PaintCommand::Image {
283 rect,
284 clip,
285 opacity,
286 image,
287 } => draw_image(
288 canvas,
289 scale_bounds(rect, scale),
290 scale_clip(clip, scale),
291 opacity,
292 image,
293 offset,
294 ),
295 }
296}
297
298pub(super) fn paint_scaled_f32_command_with_offset(
299 canvas: &mut Canvas<'_>,
300 fonts: &mut FontCtx,
301 command: PaintCommand<'_>,
302 scale: f32,
303 offset: PaintOffset,
304) {
305 if scale == 1.0 {
306 paint_command_with_offset(canvas, fonts, command, offset);
307 return;
308 }
309
310 match command {
311 PaintCommand::Rect {
312 rect,
313 clip,
314 opacity,
315 paint,
316 gradient,
317 radii,
318 } => fill_rounded_rect(
319 canvas,
320 scale_bounds_f32(rect, scale),
321 scale_clip_f32(clip, scale),
322 opacity,
323 scale_corner_radius_f32(radii, scale),
324 paint,
325 gradient,
326 offset,
327 ),
328 PaintCommand::Border {
329 rect,
330 clip,
331 opacity,
332 paint,
333 gradient,
334 widths,
335 radii,
336 } => stroke_rounded_rect(
337 canvas,
338 scale_bounds_f32(rect, scale),
339 scale_clip_f32(clip, scale),
340 opacity,
341 scale_border_width_f32(widths, scale),
342 scale_corner_radius_f32(radii, scale),
343 paint,
344 gradient,
345 offset,
346 ),
347 PaintCommand::Text {
348 rect,
349 clip,
350 opacity,
351 text,
352 } => {
353 let text = scaled_text_f32(text, scale);
354 draw_text(
355 canvas,
356 fonts,
357 scale_bounds_f32(rect, scale),
358 scale_clip_f32(clip, scale),
359 opacity,
360 &text,
361 offset,
362 );
363 }
364 PaintCommand::RichText {
365 rect,
366 clip,
367 opacity,
368 text,
369 } => {
370 let text = scaled_rich_text_f32(text, scale);
371 draw_rich_text(
372 canvas,
373 fonts,
374 scale_bounds_f32(rect, scale),
375 scale_clip_f32(clip, scale),
376 opacity,
377 &text,
378 offset,
379 );
380 }
381 PaintCommand::Image {
382 rect,
383 clip,
384 opacity,
385 image,
386 } => draw_image(
387 canvas,
388 scale_bounds_f32(rect, scale),
389 scale_clip_f32(clip, scale),
390 opacity,
391 image,
392 offset,
393 ),
394 }
395}
396
397fn scale_bounds(bounds: Bounds, scale: u32) -> Bounds {
398 Bounds {
399 x: scale_value(bounds.x, scale),
400 y: scale_value(bounds.y, scale),
401 width: scale_value(bounds.width, scale),
402 height: scale_value(bounds.height, scale),
403 }
404}
405
406fn scale_value(value: u32, scale: u32) -> u32 {
407 value.saturating_mul(scale)
408}
409
410fn scale_clip(clip: Clip, scale: u32) -> Clip {
411 let mut scaled = Clip::rect(scale_bounds(clip.bounds(), scale));
412 for rounded in clip.rounded[..usize::from(clip.rounded_len)].iter() {
413 scaled = scaled
414 .with_rounded_rect(
415 scale_bounds(rounded.rect, scale),
416 rounded.radii.scaled(scale),
417 )
418 .expect("scaled rounded clip remains inside scaled bounds");
419 }
420 scaled
421}
422
423fn scale_bounds_f32(bounds: Bounds, scale: f32) -> Bounds {
424 let x = scale_floor_u32(bounds.x, scale);
425 let y = scale_floor_u32(bounds.y, scale);
426 let right = scale_ceil_u32(bounds.right(), scale);
427 let bottom = scale_ceil_u32(bounds.bottom(), scale);
428 Bounds {
429 x,
430 y,
431 width: right.saturating_sub(x),
432 height: bottom.saturating_sub(y),
433 }
434}
435
436fn scale_value_f32(value: u32, scale: f32) -> u32 {
437 clamp_scaled_u32(f64::from(value) * f64::from(scale), FloatRound::Round)
438}
439
440fn scale_floor_u32(value: u32, scale: f32) -> u32 {
441 clamp_scaled_u32(f64::from(value) * f64::from(scale), FloatRound::Floor)
442}
443
444fn scale_ceil_u32(value: u32, scale: f32) -> u32 {
445 clamp_scaled_u32(f64::from(value) * f64::from(scale), FloatRound::Ceil)
446}
447
448#[derive(Clone, Copy)]
449enum FloatRound {
450 Floor,
451 Round,
452 Ceil,
453}
454
455fn clamp_scaled_u32(value: f64, round: FloatRound) -> u32 {
456 if !value.is_finite() || value <= 0.0 {
457 return 0;
458 }
459 let rounded = match round {
460 FloatRound::Floor => value.floor(),
461 FloatRound::Round => value.round(),
462 FloatRound::Ceil => value.ceil(),
463 };
464 rounded.min(f64::from(u32::MAX)) as u32
465}
466
467fn scale_clip_f32(clip: Clip, scale: f32) -> Clip {
468 let mut scaled = Clip::rect(scale_bounds_f32(clip.bounds(), scale));
469 for rounded in clip.rounded[..usize::from(clip.rounded_len)].iter() {
470 if let Some(next) = scaled.with_rounded_rect(
471 scale_bounds_f32(rounded.rect, scale),
472 scale_corner_radius_f32(rounded.radii, scale),
473 ) {
474 scaled = next;
475 }
476 }
477 scaled
478}
479
480fn scale_corner_radius_f32(radii: CornerRadius, scale: f32) -> CornerRadius {
481 CornerRadius {
482 top_left: scale_value_f32(radii.top_left, scale),
483 top_right: scale_value_f32(radii.top_right, scale),
484 bottom_right: scale_value_f32(radii.bottom_right, scale),
485 bottom_left: scale_value_f32(radii.bottom_left, scale),
486 }
487}
488
489fn scale_border_width_f32(widths: BorderWidth, scale: f32) -> BorderWidth {
490 BorderWidth {
491 top: scale_value_f32(widths.top, scale),
492 right: scale_value_f32(widths.right, scale),
493 bottom: scale_value_f32(widths.bottom, scale),
494 left: scale_value_f32(widths.left, scale),
495 }
496}
497
498pub(super) fn scaled_text(text: &Text, scale: u32) -> Text {
499 let mut text = text.clone();
500 scale_text_style(&mut text.style, scale);
501 text
502}
503
504pub(super) fn scaled_rich_text(text: &RichText, scale: u32) -> RichText {
505 let mut text = text.clone();
506 text.runs = text
507 .runs
508 .iter()
509 .cloned()
510 .map(|mut run| {
511 scale_text_style(&mut run.style, scale);
512 run
513 })
514 .collect();
515 text
516}
517
518fn scale_text_style(style: &mut TextStyle, scale: u32) {
519 style.size = scale_value(style.size, scale);
520}
521
522pub(super) fn scaled_text_f32(text: &Text, scale: f32) -> Text {
523 let mut text = text.clone();
524 scale_text_style_f32(&mut text.style, scale);
525 text
526}
527
528fn scaled_rich_text_f32(text: &RichText, scale: f32) -> RichText {
529 let mut text = text.clone();
530 text.runs = text
531 .runs
532 .iter()
533 .cloned()
534 .map(|mut run| {
535 scale_text_style_f32(&mut run.style, scale);
536 run
537 })
538 .collect();
539 text
540}
541
542fn scale_text_style_f32(style: &mut TextStyle, scale: f32) {
543 style.size = scale_value_f32(style.size, scale).max(1);
544}