1use std::mem::MaybeUninit;
30
31use bevy::{
32 asset::{AssetId, Assets, Handle},
33 color::Color,
34 ecs::{
35 component::Component,
36 entity::Entity,
37 query::{With, Without},
38 system::{Commands, Query, ResMut},
39 },
40 log::trace,
41 math::{bounding::Aabb2d, Rect, UVec2, Vec2, Vec3},
42 prelude::*,
43 render::{camera::Camera, texture::Image},
44 sprite::TextureAtlasLayout,
45 utils::default,
46 window::PrimaryWindow,
47};
48use bytemuck::{Pod, Zeroable};
49
50use crate::{
51 render::{ExtractedCanvas, ExtractedText, PreparedPrimitive},
52 render_context::{ImageScaling, RenderContext, TextLayout},
53 ShapeRef,
54};
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub(crate) struct PrimitiveInfo {
58 pub row_count: u32,
60 pub sub_prim_count: u32,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[repr(u8)]
76pub enum GpuPrimitiveKind {
77 Rect = 0,
79 Glyph = 1,
82 Line = 2,
84 QuarterPie = 3,
86}
87
88#[derive(Debug, Clone, Copy)]
96pub enum Primitive {
97 Line(LinePrimitive),
99 Rect(RectPrimitive),
102 Text(TextPrimitive),
104 QuarterPie(QuarterPiePrimitive),
105}
106
107impl Primitive {
108 pub fn gpu_kind(&self) -> GpuPrimitiveKind {
110 match self {
111 Primitive::Line(_) => GpuPrimitiveKind::Line,
112 Primitive::Rect(_) => GpuPrimitiveKind::Rect,
113 Primitive::Text(_) => GpuPrimitiveKind::Glyph,
114 Primitive::QuarterPie(_) => GpuPrimitiveKind::QuarterPie,
115 }
116 }
117
118 pub fn aabb(&self) -> Aabb2d {
124 match self {
125 Primitive::Line(l) => l.aabb(),
126 Primitive::Rect(r) => r.aabb(),
127 Primitive::Text(_) => panic!("Cannot compute text AABB intrinsically."),
128 Primitive::QuarterPie(q) => q.aabb(),
129 }
130 }
131
132 pub fn is_textured(&self) -> bool {
134 match self {
135 Primitive::Line(_) => false,
136 Primitive::Rect(r) => r.is_textured(),
137 Primitive::Text(_) => false, Primitive::QuarterPie(_) => false,
139 }
140 }
141
142 pub fn is_bordered(&self) -> bool {
144 match self {
145 Primitive::Line(l) => l.is_bordered(),
146 Primitive::Rect(r) => r.is_bordered(),
147 Primitive::Text(_) => false,
148 Primitive::QuarterPie(_) => false,
149 }
150 }
151
152 pub(crate) fn info(&self, texts: &[ExtractedText]) -> PrimitiveInfo {
154 match &self {
155 Primitive::Line(l) => l.info(),
156 Primitive::Rect(r) => r.info(),
157 Primitive::Text(t) => t.info(texts),
158 Primitive::QuarterPie(q) => q.info(),
159 }
160 }
161
162 pub(crate) fn write(
168 &self,
169 texts: &[ExtractedText],
170 prim: &mut [MaybeUninit<f32>],
171 canvas_translation: Vec2,
172 scale_factor: f32,
173 ) {
174 match &self {
175 Primitive::Line(l) => l.write(prim, canvas_translation, scale_factor),
176 Primitive::Rect(r) => r.write(prim, canvas_translation, scale_factor),
177 Primitive::Text(t) => t.write(texts, prim, canvas_translation, scale_factor),
178 Primitive::QuarterPie(q) => q.write(prim, canvas_translation, scale_factor),
179 };
180 }
181}
182
183impl From<LinePrimitive> for Primitive {
184 fn from(line: LinePrimitive) -> Self {
185 Self::Line(line)
186 }
187}
188
189impl From<RectPrimitive> for Primitive {
190 fn from(rect: RectPrimitive) -> Self {
191 Self::Rect(rect)
192 }
193}
194
195impl From<TextPrimitive> for Primitive {
196 fn from(text: TextPrimitive) -> Self {
197 Self::Text(text)
198 }
199}
200
201impl From<QuarterPiePrimitive> for Primitive {
202 fn from(qpie: QuarterPiePrimitive) -> Self {
203 Self::QuarterPie(qpie)
204 }
205}
206
207#[derive(Debug, Default, Clone, Copy)]
211pub struct LinePrimitive {
212 pub start: Vec2,
214 pub end: Vec2,
216 pub color: Color,
218 pub thickness: f32,
223 pub border_width: f32,
226 pub border_color: Color,
228}
229
230impl LinePrimitive {
231 pub fn aabb(&self) -> Aabb2d {
233 let dir = (self.end - self.start).normalize();
234 let tg = Vec2::new(-dir.y, dir.x);
235 let e = self.thickness / 2.;
236 let p0 = self.start + tg * e;
237 let p1 = self.start - tg * e;
238 let p2 = self.end + tg * e;
239 let p3 = self.end - tg * e;
240 let min = p0.min(p1).min(p2).min(p3);
241 let max = p0.max(p1).max(p2).max(p3);
242 Aabb2d { min, max }
243 }
244
245 pub fn is_bordered(&self) -> bool {
247 self.border_width > 0.
248 }
249
250 fn info(&self) -> PrimitiveInfo {
251 PrimitiveInfo {
252 row_count: 6 + if self.is_bordered() { 2 } else { 0 },
253 sub_prim_count: 1,
254 }
255 }
256
257 fn write(&self, prim: &mut [MaybeUninit<f32>], canvas_translation: Vec2, scale_factor: f32) {
258 prim[0].write((self.start.x + canvas_translation.x) * scale_factor);
259 prim[1].write((self.start.y + canvas_translation.y) * scale_factor);
260 prim[2].write((self.end.x + canvas_translation.x) * scale_factor);
261 prim[3].write((self.end.y + canvas_translation.y) * scale_factor);
262 prim[4].write(bytemuck::cast(self.color.to_linear().as_u32()));
263 prim[5].write(self.thickness * scale_factor);
264 if self.is_bordered() {
265 assert_eq!(8, prim.len());
266 prim[6].write(self.border_width * scale_factor);
267 prim[7].write(bytemuck::cast(self.border_color.to_linear().as_u32()));
268 } else {
269 assert_eq!(6, prim.len());
270 }
271 }
272}
273
274#[derive(Debug, Default, Clone, Copy)]
277pub struct RectPrimitive {
278 pub rect: Rect,
283 pub radius: f32,
285 pub color: Color,
287 pub image: Option<AssetId<Image>>,
289 pub image_size: Vec2,
291 pub image_scaling: ImageScaling,
293 pub flip_x: bool,
295 pub flip_y: bool,
297 pub border_width: f32,
300 pub border_color: Color,
302}
303
304impl RectPrimitive {
305 const ROW_COUNT_BASE: u32 = 6;
307 const ROW_COUNT_TEX: u32 = 4;
310 const ROW_COUNT_BORDER: u32 = 2;
314
315 pub fn aabb(&self) -> Aabb2d {
317 Aabb2d {
318 min: self.rect.min,
319 max: self.rect.max,
320 }
321 }
322
323 pub const fn is_textured(&self) -> bool {
327 self.image.is_some()
328 }
329
330 pub fn is_bordered(&self) -> bool {
332 self.border_width > 0.
333 }
334
335 #[inline]
336 fn row_count(&self) -> u32 {
337 let mut rows = Self::ROW_COUNT_BASE;
338 if self.is_textured() {
339 rows += Self::ROW_COUNT_TEX;
340 }
341 if self.is_bordered() {
342 rows += Self::ROW_COUNT_BORDER;
343 }
344 rows
345 }
346
347 fn info(&self) -> PrimitiveInfo {
348 PrimitiveInfo {
349 row_count: self.row_count(),
350 sub_prim_count: 1,
351 }
352 }
353
354 fn write(&self, prim: &mut [MaybeUninit<f32>], canvas_translation: Vec2, scale_factor: f32) {
355 assert_eq!(
356 self.row_count() as usize,
357 prim.len(),
358 "Invalid buffer size {} to write RectPrimitive (needs {})",
359 prim.len(),
360 self.row_count()
361 );
362
363 let half_min = self.rect.min * (0.5 * scale_factor);
364 let half_max = self.rect.max * (0.5 * scale_factor);
365 let center = half_min + half_max + canvas_translation * scale_factor;
366 let half_size = half_max - half_min;
367 prim[0].write(center.x);
368 prim[1].write(center.y);
369 prim[2].write(half_size.x);
370 prim[3].write(half_size.y);
371 prim[4].write(self.radius * scale_factor);
372 prim[5].write(bytemuck::cast(self.color.to_linear().as_u32()));
373 let mut idx = 6;
374 if self.is_textured() {
375 prim[idx + 0].write(0.5);
376 prim[idx + 1].write(0.5);
377 prim[idx + 2].write(1. / self.image_size.x);
378 prim[idx + 3].write(1. / self.image_size.y);
379 idx += 4;
380 }
381 if self.is_bordered() {
382 prim[idx + 0].write(self.border_width * scale_factor);
383 prim[idx + 1].write(bytemuck::cast(self.border_color.to_linear().as_u32()));
384 }
385 }
386}
387
388#[derive(Debug, Clone, Copy)]
397pub struct TextPrimitive {
398 pub id: u32,
400 pub rect: Rect,
402}
403
404impl TextPrimitive {
405 pub const ROW_PER_GLYPH: u32 = RectPrimitive::ROW_COUNT_BASE + RectPrimitive::ROW_COUNT_TEX;
408
409 pub fn aabb(&self, canvas: &ExtractedCanvas) -> Aabb2d {
411 let text = &canvas.texts[self.id as usize];
412 let mut aabb = Aabb2d {
413 min: self.rect.min,
414 max: self.rect.max,
415 };
416 trace!("Text #{:?} aabb={:?}", self.id, aabb);
417 for glyph in &text.glyphs {
418 aabb.min = aabb.min.min(self.rect.min + glyph.offset);
419 aabb.max = aabb.max.max(self.rect.min + glyph.offset + glyph.size);
420 trace!(
421 " > add glyph offset={:?} size={:?}, new aabb {:?}",
422 glyph.offset,
423 glyph.size,
424 aabb
425 );
426 }
427 aabb
428 }
429
430 fn info(&self, texts: &[ExtractedText]) -> PrimitiveInfo {
431 let index = self.id as usize;
432 if index < texts.len() {
433 let glyph_count = texts[index].glyphs.len() as u32;
434 PrimitiveInfo {
435 row_count: Self::ROW_PER_GLYPH,
436 sub_prim_count: glyph_count,
437 }
438 } else {
439 PrimitiveInfo {
440 row_count: 0,
441 sub_prim_count: 0,
442 }
443 }
444 }
445
446 fn write(
447 &self,
448 texts: &[ExtractedText],
449 prim: &mut [MaybeUninit<f32>],
450 canvas_translation: Vec2,
451 scale_factor: f32,
452 ) {
453 let index = self.id as usize;
454 let glyphs = &texts[index].glyphs;
455 let glyph_count = glyphs.len();
456 assert_eq!(glyph_count * Self::ROW_PER_GLYPH as usize, prim.len());
457 let mut ip = 0;
458 for i in 0..glyph_count {
460 let x = glyphs[i].offset.x + (self.rect.min.x + canvas_translation.x) * scale_factor;
461 let y = glyphs[i].offset.y + (self.rect.min.y + canvas_translation.y) * scale_factor;
462 let hw = glyphs[i].size.x / 2.0;
463 let hh = glyphs[i].size.y / 2.0;
464
465 let uv_x = glyphs[i].uv_rect.min.x / 1024.0;
476 let uv_y = glyphs[i].uv_rect.min.y / 1024.0;
477 let uv_w = glyphs[i].uv_rect.max.x / 1024.0 - uv_x;
478 let uv_h = glyphs[i].uv_rect.max.y / 1024.0 - uv_y;
479
480 prim[ip + 0].write(x.round() + hw);
493 prim[ip + 1].write(y.round() + hh);
494
495 prim[ip + 2].write(hw);
497 prim[ip + 3].write(hh);
498
499 prim[ip + 4].write(0.);
501
502 prim[ip + 5].write(bytemuck::cast(glyphs[i].color));
504
505 prim[ip + 6].write(uv_x + uv_w / 2.0);
507 prim[ip + 7].write(uv_y + uv_h / 2.0);
508
509 prim[ip + 8].write(1.0 / 1024.0);
511 prim[ip + 9].write(1.0 / 1024.0);
512
513 ip += Self::ROW_PER_GLYPH as usize;
514 }
515 }
516}
517
518#[derive(Debug, Clone, Copy)]
519pub struct QuarterPiePrimitive {
520 pub origin: Vec2,
522 pub radii: Vec2,
524 pub color: Color,
526 pub flip_x: bool,
528 pub flip_y: bool,
530}
531
532impl Default for QuarterPiePrimitive {
533 fn default() -> Self {
534 Self {
535 origin: Vec2::ZERO,
536 radii: Vec2::ONE,
537 color: Color::default(),
538 flip_x: false,
539 flip_y: false,
540 }
541 }
542}
543
544impl QuarterPiePrimitive {
545 const ROW_COUNT: u32 = 5;
547
548 pub fn aabb(&self) -> Aabb2d {
549 Aabb2d {
550 min: self.origin - self.radii,
551 max: self.origin + self.radii,
552 }
553 }
554
555 pub fn center(&self) -> Vec3 {
557 self.origin.extend(0.)
558 }
559
560 #[inline]
561 const fn row_count(&self) -> u32 {
562 Self::ROW_COUNT
563 }
564
565 fn info(&self) -> PrimitiveInfo {
566 PrimitiveInfo {
567 row_count: self.row_count(),
568 sub_prim_count: 1,
569 }
570 }
571
572 fn write(&self, prim: &mut [MaybeUninit<f32>], canvas_translation: Vec2, scale_factor: f32) {
573 assert_eq!(self.row_count() as usize, prim.len());
574 let radii_mask = BVec2::new(self.flip_x, self.flip_y);
575 let signed_radii = Vec2::select(radii_mask, -self.radii, self.radii);
576 prim[0].write((self.origin.x + canvas_translation.x) * scale_factor);
577 prim[1].write((self.origin.y + canvas_translation.y) * scale_factor);
578 prim[2].write(signed_radii.x * scale_factor);
579 prim[3].write(signed_radii.y * scale_factor);
580 prim[4].write(bytemuck::cast(self.color.to_linear().as_u32()));
581 }
582}
583
584#[derive(Component)]
592pub struct Canvas {
593 rect: Rect,
597 pub background_color: Option<Color>,
606 primitives: Vec<Primitive>,
608 pub(crate) text_layouts: Vec<TextLayout>,
610 pub(crate) atlas_layout: Handle<TextureAtlasLayout>,
613}
614
615impl Default for Canvas {
616 fn default() -> Self {
617 Self {
618 rect: Rect::default(),
619 background_color: None,
620 primitives: vec![],
621 text_layouts: vec![],
622 atlas_layout: Handle::default(),
623 }
624 }
625}
626
627impl Canvas {
628 pub fn new(rect: Rect) -> Self {
633 Self { rect, ..default() }
634 }
635
636 pub fn set_rect(&mut self, rect: Rect) {
641 self.rect = rect;
647 }
648
649 pub fn rect(&self) -> Rect {
653 self.rect
654 }
655
656 pub fn clear(&mut self) {
663 self.primitives.clear();
664 self.text_layouts.clear(); if let Some(color) = self.background_color {
667 self.draw(RectPrimitive {
668 rect: self.rect,
669 color,
670 ..default()
671 });
672 }
673 }
674
675 #[inline]
683 pub fn draw<'a>(&'a mut self, prim: impl Into<Primitive>) -> ShapeRef<'a> {
684 let prim = prim.into();
685 self.primitives.push(prim);
686 let sref = ShapeRef {
687 prim: self.primitives.last_mut().unwrap(),
688 };
689 sref
690 }
691
692 pub fn render_context(&mut self) -> RenderContext {
694 RenderContext::new(self)
695 }
696
697 pub(crate) fn finish(&mut self) {
698 }
700
701 pub(crate) fn finish_layout(&mut self, mut layout: TextLayout) -> u32 {
702 let id = self.text_layouts.len() as u32;
703 trace!("finish_layout() for text #{}", id);
704 layout.id = id;
705 self.text_layouts.push(layout);
706 id
707 }
708
709 pub(crate) fn buffer(&self) -> &Vec<Primitive> {
711 &self.primitives
712 }
713
714 pub(crate) fn text_layouts(&self) -> &[TextLayout] {
715 &self.text_layouts[..]
716 }
717
718 pub(crate) fn text_layouts_mut(&mut self) -> &mut [TextLayout] {
719 &mut self.text_layouts[..]
720 }
721
722 pub(crate) fn has_text(&self) -> bool {
723 !self.text_layouts.is_empty()
724 }
725}
726
727pub fn update_canvas_from_ortho_camera(mut query: Query<(&mut Canvas, &OrthographicProjection)>) {
734 trace!("PreUpdate: update_canvas_from_ortho_camera()");
735 for (mut canvas, ortho) in query.iter_mut() {
736 trace!("ortho canvas rect = {:?}", ortho.area);
737 canvas.set_rect(ortho.area);
738 }
739}
740
741#[derive(Default, Clone, Copy, Component)]
745pub struct TileConfig {}
746
747#[derive(Debug, Default, Clone, Copy, Pod, Zeroable)]
748#[repr(C)]
749pub(crate) struct OffsetAndCount {
750 pub offset: u32,
752 pub count: u32,
754}
755
756#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
765#[repr(transparent)]
766pub(crate) struct PackedPrimitiveIndex(pub u32);
767
768impl PackedPrimitiveIndex {
769 pub fn new(index: u32, kind: GpuPrimitiveKind, textured: bool, bordered: bool) -> Self {
771 let textured = (textured as u32) << 31;
772 let bordered = (bordered as u32) << 27;
773 let value = (index & 0x07FF_FFFF) | (kind as u32) << 28 | textured | bordered;
774 Self(value)
775 }
776}
777
778#[derive(Clone, Copy)]
779struct AssignedTile {
780 pub tile_index: i32,
781 pub prim_index: PackedPrimitiveIndex,
782}
783
784#[derive(Default, Clone, Component)]
790pub struct Tiles {
791 pub(crate) tile_size: UVec2,
793 pub(crate) dimensions: UVec2,
798 pub(crate) primitives: Vec<PackedPrimitiveIndex>,
804 pub(crate) offset_and_count: Vec<OffsetAndCount>,
806 assigned_tiles: Vec<AssignedTile>,
808}
809
810impl Tiles {
811 pub fn update_size(&mut self, screen_size: UVec2) {
816 self.tile_size = UVec2::new(8, 8);
818
819 self.dimensions = (screen_size.as_vec2() / self.tile_size.as_vec2())
820 .ceil()
821 .as_uvec2();
822
823 assert!(self.dimensions.x * self.tile_size.x >= screen_size.x);
824 assert!(self.dimensions.y * self.tile_size.y >= screen_size.y);
825
826 self.primitives.clear();
827 self.offset_and_count.clear();
828 self.offset_and_count
829 .reserve(self.dimensions.x as usize * self.dimensions.y as usize);
830
831 trace!(
832 "Resized Tiles at tile_size={:?} dim={:?} and cleared buffers",
833 self.tile_size,
834 self.dimensions
835 );
836 }
837
838 pub(crate) fn assign_to_tiles(&mut self, primitives: &[PreparedPrimitive], screen_size: Vec2) {
846 let tile_size = self.tile_size.as_vec2();
847
848 let oc_extra = self.dimensions.x as usize * self.dimensions.y as usize;
849 self.offset_and_count.reserve(oc_extra);
850
851 self.assigned_tiles.reserve(primitives.len() * 4);
854
855 for prim in primitives {
857 let uv_min = (prim.aabb.min.clamp(Vec2::ZERO, screen_size) / tile_size)
859 .floor()
860 .as_ivec2();
861 let mut uv_max = (prim.aabb.max.clamp(Vec2::ZERO, screen_size) / tile_size)
862 .ceil()
863 .as_ivec2();
864 if prim.aabb.max.x == tile_size.x * uv_max.x as f32 {
865 uv_max.x -= 1;
867 }
868 if prim.aabb.max.y == tile_size.y * uv_max.y as f32 {
869 uv_max.y -= 1;
871 }
872
873 self.assigned_tiles
874 .reserve((uv_max.y - uv_min.y + 1) as usize * (uv_max.x - uv_min.x + 1) as usize);
875
876 for ty in uv_min.y..=uv_max.y {
879 let base_tile_index = ty * self.dimensions.x as i32;
880 for tx in uv_min.x..=uv_max.x {
881 let tile_index = base_tile_index + tx;
882 self.assigned_tiles.push(AssignedTile {
883 tile_index,
884 prim_index: prim.prim_index,
885 });
886 }
887 }
888 }
889
890 self.assigned_tiles.sort_by_key(|at| at.tile_index);
894
895 self.primitives.reserve(self.assigned_tiles.len());
897 let mut ti = -1;
898 let mut offset = 0;
899 let mut count = 0;
900 for at in &self.assigned_tiles {
901 if at.tile_index != ti {
902 if count > 0 {
903 self.offset_and_count.push(OffsetAndCount {
905 offset: offset as u32,
906 count,
907 });
908 }
909 for _ in ti + 1..at.tile_index {
911 self.offset_and_count.push(OffsetAndCount {
912 offset: offset as u32,
913 count: 0,
914 });
915 }
916 offset = self.primitives.len() as u32;
917 count = 0;
918 ti = at.tile_index;
919 }
920
921 self.primitives.push(at.prim_index);
922 count += 1;
923 }
924 if count > 0 {
926 self.offset_and_count.push(OffsetAndCount {
927 offset: offset as u32,
928 count,
929 });
930 }
931 for _ in ti + 1..oc_extra as i32 {
933 self.offset_and_count.push(OffsetAndCount {
934 offset: offset as u32,
935 count: 0,
936 });
937 }
938
939 self.assigned_tiles.clear();
941 }
942}
943
944pub fn spawn_missing_tiles_components(
947 mut commands: Commands,
948 cameras: Query<(Entity, Option<&TileConfig>, &Camera), (With<Canvas>, Without<Tiles>)>,
949) {
950 for (entity, config, camera) in &cameras {
951 if !camera.is_active {
952 continue;
953 }
954
955 let config = config.copied().unwrap_or_default();
956 commands.entity(entity).insert((Tiles::default(), config));
957 }
958}
959
960pub fn resize_tiles_to_camera_render_target(
961 mut views: Query<(&Camera, &TileConfig, &mut Tiles), With<Canvas>>,
962) {
963 for (camera, _tile_config, tiles) in &mut views {
965 let Some(screen_size) = camera.physical_viewport_size() else {
966 continue;
967 };
968
969 let tiles = tiles.into_inner();
971 tiles.update_size(screen_size);
972 }
973}
974
975pub fn allocate_atlas_layouts(
976 mut query: Query<&mut Canvas>,
977 mut layouts: ResMut<Assets<TextureAtlasLayout>>,
978) {
979 for mut canvas in query.iter_mut() {
980 let size = UVec2::splat(1024);
982
983 if canvas.atlas_layout == Handle::<TextureAtlasLayout>::default() {
985 canvas.atlas_layout = layouts.add(TextureAtlasLayout::new_empty(size));
986 }
987 }
988}
989
990fn aspect_width(size: Vec2, content_height: f32) -> f32 {
992 size.x.max(0.) / size.y.max(1.) * content_height.max(0.)
993}
994
995fn fit_width(size: Vec2, content_size: Vec2, stretch_height: bool) -> Vec2 {
999 Vec2::new(
1000 content_size.x,
1001 if stretch_height {
1002 content_size.y
1003 } else {
1004 aspect_height(size, content_size.x)
1005 },
1006 )
1007}
1008
1009fn aspect_height(size: Vec2, content_width: f32) -> f32 {
1011 size.y.max(0.) / size.x.max(1.) * content_width.max(0.)
1012}
1013
1014fn fit_height(size: Vec2, content_size: Vec2, stretch_width: bool) -> Vec2 {
1018 Vec2::new(
1019 if stretch_width {
1020 content_size.x
1021 } else {
1022 aspect_width(size, content_size.y)
1023 },
1024 content_size.y,
1025 )
1026}
1027
1028fn fit_any(size: Vec2, content_size: Vec2, stretch_other: bool) -> Vec2 {
1033 let aspect = size.x.max(0.) / size.y.max(1.);
1034 let content_aspect = content_size.x.max(0.) / content_size.y.max(1.);
1035 if aspect >= content_aspect {
1036 fit_height(size, content_size, stretch_other)
1037 } else {
1038 fit_width(size, content_size, stretch_other)
1039 }
1040}
1041
1042pub fn process_images(
1048 images: Res<Assets<Image>>,
1049 q_window: Query<&Window, With<PrimaryWindow>>,
1050 mut q_canvas: Query<&mut Canvas>,
1051) {
1052 let Ok(primary_window) = q_window.get_single() else {
1054 return;
1055 };
1056 let scale_factor = primary_window.scale_factor() as f32;
1057
1058 for mut canvas in q_canvas.iter_mut() {
1059 for prim in &mut canvas.primitives {
1060 let Primitive::Rect(rect) = prim else {
1061 continue;
1062 };
1063 let Some(id) = rect.image else {
1064 continue;
1065 };
1066 if let Some(image) = images.get(id) {
1067 let image_size = Vec2::new(
1068 image.texture_descriptor.size.width as f32,
1069 image.texture_descriptor.size.height as f32,
1070 );
1071 let content_size = rect.rect.size() * scale_factor;
1072 rect.image_size = match rect.image_scaling {
1073 ImageScaling::Uniform(ratio) => image_size * ratio,
1074 ImageScaling::FitWidth(stretch_height) => {
1075 fit_width(image_size, content_size, stretch_height)
1076 }
1077 ImageScaling::FitHeight(stretch_width) => {
1078 fit_height(image_size, content_size, stretch_width)
1079 }
1080 ImageScaling::Fit(stretch_other) => {
1081 fit_any(image_size, content_size, stretch_other)
1082 }
1083 ImageScaling::Stretch => content_size,
1084 }
1085 } else {
1086 warn!("Unknown image asset ID {:?}; skipped.", id);
1087 rect.image = None;
1088 }
1089 }
1090 }
1091}
1092
1093#[cfg(test)]
1094mod tests {
1095 use super::*;
1096
1097 #[test]
1098 fn tiles() {
1099 let mut tiles = Tiles::default();
1100 tiles.update_size(UVec2::new(32, 64));
1101 assert_eq!(tiles.dimensions, UVec2::new(4, 8));
1102 assert!(tiles.primitives.is_empty());
1103 assert!(tiles.offset_and_count.is_empty());
1104 assert_eq!(tiles.offset_and_count.capacity(), 32);
1105
1106 let prim_index = PackedPrimitiveIndex::new(42, GpuPrimitiveKind::Line, true, false);
1107 tiles.assign_to_tiles(
1108 &[PreparedPrimitive {
1109 aabb: Aabb2d {
1111 min: Vec2::new(8., 16.),
1112 max: Vec2::new(16., 32.),
1113 },
1114 prim_index,
1115 }],
1116 Vec2::new(256., 128.),
1118 );
1119
1120 assert_eq!(tiles.primitives.len(), 2);
1121 assert_eq!(tiles.primitives[0], prim_index);
1122 assert_eq!(tiles.primitives[1], prim_index);
1123
1124 assert_eq!(tiles.offset_and_count.len(), 32);
1125 for (idx, oc) in tiles.offset_and_count.iter().enumerate() {
1126 if idx == 9 || idx == 13 {
1127 assert_eq!(oc.count, 1);
1128 assert_eq!(oc.offset, if idx == 9 { 0 } else { 1 });
1129 } else {
1130 assert_eq!(oc.count, 0);
1131 }
1132 }
1133 }
1134
1135 #[test]
1136 fn aspect() {
1137 assert_eq!(aspect_width(Vec2::ZERO, 0.), 0.);
1139 assert_eq!(aspect_height(Vec2::ZERO, 0.), 0.);
1140 assert_eq!(aspect_width(Vec2::ZERO, 1.), 0.);
1141 assert_eq!(aspect_height(Vec2::ZERO, 1.), 0.);
1142 assert_eq!(aspect_width(Vec2::ONE, 0.), 0.);
1143 assert_eq!(aspect_height(Vec2::ONE, 0.), 0.);
1144
1145 assert_eq!(aspect_width(Vec2::new(256., 64.), 128.), 512.);
1147 assert_eq!(aspect_height(Vec2::new(256., 64.), 512.), 128.);
1148
1149 assert_eq!(aspect_width(Vec2::new(256., 128.), 64.), 128.);
1151 assert_eq!(aspect_height(Vec2::new(256., 64.), 128.), 32.);
1152 }
1153
1154 #[test]
1155 fn fit() {
1156 assert_eq!(fit_width(Vec2::ZERO, Vec2::ZERO, false), Vec2::ZERO);
1158 assert_eq!(fit_height(Vec2::ZERO, Vec2::ZERO, false), Vec2::ZERO);
1159 assert_eq!(fit_any(Vec2::ZERO, Vec2::ZERO, false), Vec2::ZERO);
1160 assert_eq!(fit_width(Vec2::ONE, Vec2::ZERO, false), Vec2::ZERO);
1161 assert_eq!(fit_height(Vec2::ONE, Vec2::ZERO, false), Vec2::ZERO);
1162 assert_eq!(fit_any(Vec2::ONE, Vec2::ZERO, false), Vec2::ZERO);
1163 assert_eq!(fit_width(Vec2::ZERO, Vec2::ZERO, true), Vec2::ZERO);
1164 assert_eq!(fit_height(Vec2::ZERO, Vec2::ZERO, true), Vec2::ZERO);
1165 assert_eq!(fit_any(Vec2::ZERO, Vec2::ZERO, true), Vec2::ZERO);
1166 assert_eq!(fit_width(Vec2::ONE, Vec2::ZERO, true), Vec2::ZERO);
1167 assert_eq!(fit_height(Vec2::ONE, Vec2::ZERO, true), Vec2::ZERO);
1168 assert_eq!(fit_any(Vec2::ONE, Vec2::ZERO, true), Vec2::ZERO);
1169
1170 assert_eq!(fit_width(Vec2::ZERO, Vec2::ONE, false), Vec2::X);
1172 assert_eq!(fit_height(Vec2::ZERO, Vec2::ONE, false), Vec2::Y);
1173 assert_eq!(fit_width(Vec2::ZERO, Vec2::ONE, true), Vec2::ONE);
1174 assert_eq!(fit_height(Vec2::ZERO, Vec2::ONE, true), Vec2::ONE);
1175
1176 assert_eq!(
1178 fit_width(Vec2::new(256., 64.), Vec2::new(512., 32.), false),
1179 Vec2::new(512., 128.)
1180 );
1181 assert_eq!(
1182 fit_height(Vec2::new(256., 64.), Vec2::new(128., 128.), false),
1183 Vec2::new(512., 128.)
1184 );
1185 assert_eq!(
1186 fit_width(Vec2::new(256., 64.), Vec2::new(512., 32.), true),
1187 Vec2::new(512., 32.)
1188 );
1189 assert_eq!(
1190 fit_height(Vec2::new(256., 64.), Vec2::new(128., 128.), true),
1191 Vec2::new(128., 128.)
1192 );
1193
1194 assert_eq!(
1196 fit_width(Vec2::new(256., 64.), Vec2::new(128., 128.), false),
1197 Vec2::new(128., 32.)
1198 );
1199 assert_eq!(
1200 fit_height(Vec2::new(256., 64.), Vec2::new(512., 32.), false),
1201 Vec2::new(128., 32.)
1202 );
1203 assert_eq!(
1204 fit_width(Vec2::new(256., 64.), Vec2::new(128., 128.), true),
1205 Vec2::new(128., 128.)
1206 );
1207 assert_eq!(
1208 fit_height(Vec2::new(256., 64.), Vec2::new(512., 32.), true),
1209 Vec2::new(512., 32.)
1210 );
1211 }
1212}