@@ -6,6 +6,10 @@ use crate::{
6
6
} ;
7
7
#[ cfg( feature = "custom_cursor" ) ]
8
8
use crate :: {
9
+ custom_cursor:: {
10
+ calculate_effective_rect, extract_and_transform_rgba_pixels, extract_rgba_pixels,
11
+ CustomCursorPlugin ,
12
+ } ,
9
13
state:: { CustomCursorCache , CustomCursorCacheKey } ,
10
14
WinitCustomCursor ,
11
15
} ;
@@ -27,21 +31,19 @@ use bevy_ecs::{
27
31
#[ cfg( feature = "custom_cursor" ) ]
28
32
use bevy_image:: { Image , TextureAtlas , TextureAtlasLayout } ;
29
33
#[ cfg( feature = "custom_cursor" ) ]
30
- use bevy_math:: { Rect , URect , Vec2 } ;
34
+ use bevy_math:: URect ;
31
35
use bevy_reflect:: { std_traits:: ReflectDefault , Reflect } ;
32
36
use bevy_utils:: HashSet ;
33
37
use bevy_window:: { SystemCursorIcon , Window } ;
34
38
#[ cfg( feature = "custom_cursor" ) ]
35
39
use tracing:: warn;
36
- #[ cfg( feature = "custom_cursor" ) ]
37
- use wgpu_types:: TextureFormat ;
38
40
39
41
pub ( crate ) struct CursorPlugin ;
40
42
41
43
impl Plugin for CursorPlugin {
42
44
fn build ( & self , app : & mut App ) {
43
45
#[ cfg( feature = "custom_cursor" ) ]
44
- app. init_resource :: < CustomCursorCache > ( ) ;
46
+ app. add_plugins ( CustomCursorPlugin ) ;
45
47
46
48
app. register_type :: < CursorIcon > ( )
47
49
. add_systems ( Last , update_cursors) ;
@@ -161,36 +163,22 @@ fn update_cursors(
161
163
continue ;
162
164
} ;
163
165
164
- let atlas_rect = texture_atlas
165
- . as_ref ( )
166
- . and_then ( |s| s. texture_rect ( & texture_atlases) )
167
- . map ( |r| r. as_rect ( ) ) ;
166
+ let ( rect, needs_sub_image) =
167
+ calculate_effective_rect ( & texture_atlases, image, texture_atlas, rect) ;
168
168
169
- let rect = match ( atlas_rect, rect) {
170
- ( None , None ) => Rect {
171
- min : Vec2 :: ZERO ,
172
- max : Vec2 :: new (
173
- image. texture_descriptor . size . width as f32 ,
174
- image. texture_descriptor . size . height as f32 ,
175
- ) ,
176
- } ,
177
- ( None , Some ( image_rect) ) => image_rect. as_rect ( ) ,
178
- ( Some ( atlas_rect) , None ) => atlas_rect,
179
- ( Some ( atlas_rect) , Some ( image_rect) ) => {
180
- let mut image_rect = image_rect. as_rect ( ) ;
181
- image_rect. min += atlas_rect. min ;
182
- image_rect. max += atlas_rect. min ;
183
- image_rect
184
- }
169
+ let maybe_rgba = if * flip_x || * flip_y || needs_sub_image {
170
+ extract_and_transform_rgba_pixels ( image, * flip_x, * flip_y, rect)
171
+ } else {
172
+ extract_rgba_pixels ( image)
185
173
} ;
186
174
187
- let Some ( rgba) = image_to_rgba_pixels ( image , * flip_x , * flip_y , rect ) else {
175
+ let Some ( rgba) = maybe_rgba else {
188
176
warn ! ( "Cursor image {handle:?} not accepted because it's not rgba8 or rgba32float format" ) ;
189
177
continue ;
190
178
} ;
191
179
192
- let width = ( rect. max . x - rect . min . x ) as u16 ;
193
- let height = ( rect. max . y - rect . min . y ) as u16 ;
180
+ let width = rect. width ( ) as u16 ;
181
+ let height = rect. height ( ) as u16 ;
194
182
let source = match WinitCustomCursor :: from_rgba (
195
183
rgba, width, height, hotspot. 0 , hotspot. 1 ,
196
184
) {
@@ -240,313 +228,3 @@ fn on_remove_cursor_icon(trigger: Trigger<OnRemove, CursorIcon>, mut commands: C
240
228
convert_system_cursor_icon ( SystemCursorIcon :: Default ) ,
241
229
) ) ) ) ;
242
230
}
243
-
244
- #[ cfg( feature = "custom_cursor" ) ]
245
- /// Returns the `image` data as a `Vec<u8>` for the specified sub-region.
246
- ///
247
- /// The image is flipped along the x and y axes if `flip_x` and `flip_y` are
248
- /// `true`, respectively.
249
- ///
250
- /// Only supports rgba8 and rgba32float formats.
251
- fn image_to_rgba_pixels ( image : & Image , flip_x : bool , flip_y : bool , rect : Rect ) -> Option < Vec < u8 > > {
252
- let image_data_as_u8s: Vec < u8 > ;
253
-
254
- let image_data = match image. texture_descriptor . format {
255
- TextureFormat :: Rgba8Unorm
256
- | TextureFormat :: Rgba8UnormSrgb
257
- | TextureFormat :: Rgba8Snorm
258
- | TextureFormat :: Rgba8Uint
259
- | TextureFormat :: Rgba8Sint => Some ( & image. data ) ,
260
- TextureFormat :: Rgba32Float => {
261
- image_data_as_u8s = image
262
- . data
263
- . chunks ( 4 )
264
- . map ( |chunk| {
265
- let chunk = chunk. try_into ( ) . unwrap ( ) ;
266
- let num = bytemuck:: cast_ref :: < [ u8 ; 4 ] , f32 > ( chunk) ;
267
- ( num * 255.0 ) as u8
268
- } )
269
- . collect :: < Vec < u8 > > ( ) ;
270
-
271
- Some ( & image_data_as_u8s)
272
- }
273
- _ => None ,
274
- } ;
275
-
276
- let image_data = image_data?;
277
-
278
- let width = ( rect. max . x - rect. min . x ) as usize ;
279
- let height = ( rect. max . y - rect. min . y ) as usize ;
280
- let mut sub_image_data = Vec :: with_capacity ( width * height * 4 ) ; // assuming 4 bytes per pixel (RGBA8)
281
-
282
- for y in 0 ..height {
283
- for x in 0 ..width {
284
- let src_x = if flip_x { width - 1 - x } else { x } ;
285
- let src_y = if flip_y { height - 1 - y } else { y } ;
286
- let index = ( ( rect. min . y as usize + src_y)
287
- * image. texture_descriptor . size . width as usize
288
- + ( rect. min . x as usize + src_x) )
289
- * 4 ;
290
- sub_image_data. extend_from_slice ( & image_data[ index..index + 4 ] ) ;
291
- }
292
- }
293
-
294
- Some ( sub_image_data)
295
- }
296
-
297
- #[ cfg( feature = "custom_cursor" ) ]
298
- #[ cfg( test) ]
299
- mod tests {
300
- use bevy_asset:: RenderAssetUsages ;
301
- use bevy_image:: Image ;
302
- use bevy_math:: Rect ;
303
- use bevy_math:: Vec2 ;
304
- use wgpu_types:: { Extent3d , TextureDimension } ;
305
-
306
- use super :: * ;
307
-
308
- fn create_image_rgba8 ( data : & [ u8 ] ) -> Image {
309
- Image :: new (
310
- Extent3d {
311
- width : 3 ,
312
- height : 3 ,
313
- depth_or_array_layers : 1 ,
314
- } ,
315
- TextureDimension :: D2 ,
316
- data. to_vec ( ) ,
317
- TextureFormat :: Rgba8UnormSrgb ,
318
- RenderAssetUsages :: default ( ) ,
319
- )
320
- }
321
-
322
- fn create_image_rgba32float ( data : & [ u8 ] ) -> Image {
323
- let float_data: Vec < f32 > = data
324
- . chunks ( 4 )
325
- . flat_map ( |chunk| {
326
- chunk
327
- . iter ( )
328
- . map ( |& x| x as f32 / 255.0 ) // convert each channel to f32
329
- . collect :: < Vec < f32 > > ( )
330
- } )
331
- . collect ( ) ;
332
-
333
- Image :: new (
334
- Extent3d {
335
- width : 3 ,
336
- height : 3 ,
337
- depth_or_array_layers : 1 ,
338
- } ,
339
- TextureDimension :: D2 ,
340
- bytemuck:: cast_slice ( & float_data) . to_vec ( ) ,
341
- TextureFormat :: Rgba32Float ,
342
- RenderAssetUsages :: default ( ) ,
343
- )
344
- }
345
-
346
- macro_rules! test_image_to_rgba_pixels {
347
- ( $name: ident, $flip_x: expr, $flip_y: expr, $rect: expr, $expected: expr) => {
348
- #[ test]
349
- fn $name( ) {
350
- let image_data: & [ u8 ] = & [
351
- // Row 1: Red, Green, Blue
352
- 255 , 0 , 0 , 255 , // Red
353
- 0 , 255 , 0 , 255 , // Green
354
- 0 , 0 , 255 , 255 , // Blue
355
- // Row 2: Yellow, Cyan, Magenta
356
- 255 , 255 , 0 , 255 , // Yellow
357
- 0 , 255 , 255 , 255 , // Cyan
358
- 255 , 0 , 255 , 255 , // Magenta
359
- // Row 3: White, Gray, Black
360
- 255 , 255 , 255 , 255 , // White
361
- 128 , 128 , 128 , 255 , // Gray
362
- 0 , 0 , 0 , 255 , // Black
363
- ] ;
364
-
365
- // RGBA8 test
366
- {
367
- let image = create_image_rgba8( image_data) ;
368
- let rect = $rect;
369
- let result = image_to_rgba_pixels( & image, $flip_x, $flip_y, rect) ;
370
- assert_eq!( result, Some ( $expected. to_vec( ) ) ) ;
371
- }
372
-
373
- // RGBA32Float test
374
- {
375
- let image = create_image_rgba32float( image_data) ;
376
- let rect = $rect;
377
- let result = image_to_rgba_pixels( & image, $flip_x, $flip_y, rect) ;
378
- assert_eq!( result, Some ( $expected. to_vec( ) ) ) ;
379
- }
380
- }
381
- } ;
382
- }
383
-
384
- test_image_to_rgba_pixels ! (
385
- no_flip_full_image,
386
- false ,
387
- false ,
388
- Rect {
389
- min: Vec2 :: ZERO ,
390
- max: Vec2 :: new( 3.0 , 3.0 )
391
- } ,
392
- [
393
- // Row 1: Red, Green, Blue
394
- 255 , 0 , 0 , 255 , // Red
395
- 0 , 255 , 0 , 255 , // Green
396
- 0 , 0 , 255 , 255 , // Blue
397
- // Row 2: Yellow, Cyan, Magenta
398
- 255 , 255 , 0 , 255 , // Yellow
399
- 0 , 255 , 255 , 255 , // Cyan
400
- 255 , 0 , 255 , 255 , // Magenta
401
- // Row 3: White, Gray, Black
402
- 255 , 255 , 255 , 255 , // White
403
- 128 , 128 , 128 , 255 , // Gray
404
- 0 , 0 , 0 , 255 , // Black
405
- ]
406
- ) ;
407
-
408
- test_image_to_rgba_pixels ! (
409
- flip_x_full_image,
410
- true ,
411
- false ,
412
- Rect {
413
- min: Vec2 :: ZERO ,
414
- max: Vec2 :: new( 3.0 , 3.0 )
415
- } ,
416
- [
417
- // Row 1 flipped: Blue, Green, Red
418
- 0 , 0 , 255 , 255 , // Blue
419
- 0 , 255 , 0 , 255 , // Green
420
- 255 , 0 , 0 , 255 , // Red
421
- // Row 2 flipped: Magenta, Cyan, Yellow
422
- 255 , 0 , 255 , 255 , // Magenta
423
- 0 , 255 , 255 , 255 , // Cyan
424
- 255 , 255 , 0 , 255 , // Yellow
425
- // Row 3 flipped: Black, Gray, White
426
- 0 , 0 , 0 , 255 , // Black
427
- 128 , 128 , 128 , 255 , // Gray
428
- 255 , 255 , 255 , 255 , // White
429
- ]
430
- ) ;
431
-
432
- test_image_to_rgba_pixels ! (
433
- flip_y_full_image,
434
- false ,
435
- true ,
436
- Rect {
437
- min: Vec2 :: ZERO ,
438
- max: Vec2 :: new( 3.0 , 3.0 )
439
- } ,
440
- [
441
- // Row 3: White, Gray, Black
442
- 255 , 255 , 255 , 255 , // White
443
- 128 , 128 , 128 , 255 , // Gray
444
- 0 , 0 , 0 , 255 , // Black
445
- // Row 2: Yellow, Cyan, Magenta
446
- 255 , 255 , 0 , 255 , // Yellow
447
- 0 , 255 , 255 , 255 , // Cyan
448
- 255 , 0 , 255 , 255 , // Magenta
449
- // Row 1: Red, Green, Blue
450
- 255 , 0 , 0 , 255 , // Red
451
- 0 , 255 , 0 , 255 , // Green
452
- 0 , 0 , 255 , 255 , // Blue
453
- ]
454
- ) ;
455
-
456
- test_image_to_rgba_pixels ! (
457
- flip_both_full_image,
458
- true ,
459
- true ,
460
- Rect {
461
- min: Vec2 :: ZERO ,
462
- max: Vec2 :: new( 3.0 , 3.0 )
463
- } ,
464
- [
465
- // Row 3 flipped: Black, Gray, White
466
- 0 , 0 , 0 , 255 , // Black
467
- 128 , 128 , 128 , 255 , // Gray
468
- 255 , 255 , 255 , 255 , // White
469
- // Row 2 flipped: Magenta, Cyan, Yellow
470
- 255 , 0 , 255 , 255 , // Magenta
471
- 0 , 255 , 255 , 255 , // Cyan
472
- 255 , 255 , 0 , 255 , // Yellow
473
- // Row 1 flipped: Blue, Green, Red
474
- 0 , 0 , 255 , 255 , // Blue
475
- 0 , 255 , 0 , 255 , // Green
476
- 255 , 0 , 0 , 255 , // Red
477
- ]
478
- ) ;
479
-
480
- test_image_to_rgba_pixels ! (
481
- no_flip_rect,
482
- false ,
483
- false ,
484
- Rect {
485
- min: Vec2 :: new( 1.0 , 1.0 ) ,
486
- max: Vec2 :: new( 3.0 , 3.0 )
487
- } ,
488
- [
489
- // Only includes part of the original image (sub-rectangle)
490
- // Row 2, columns 2-3: Cyan, Magenta
491
- 0 , 255 , 255 , 255 , // Cyan
492
- 255 , 0 , 255 , 255 , // Magenta
493
- // Row 3, columns 2-3: Gray, Black
494
- 128 , 128 , 128 , 255 , // Gray
495
- 0 , 0 , 0 , 255 , // Black
496
- ]
497
- ) ;
498
-
499
- test_image_to_rgba_pixels ! (
500
- flip_x_rect,
501
- true ,
502
- false ,
503
- Rect {
504
- min: Vec2 :: new( 1.0 , 1.0 ) ,
505
- max: Vec2 :: new( 3.0 , 3.0 )
506
- } ,
507
- [
508
- // Row 2 flipped: Magenta, Cyan
509
- 255 , 0 , 255 , 255 , // Magenta
510
- 0 , 255 , 255 , 255 , // Cyan
511
- // Row 3 flipped: Black, Gray
512
- 0 , 0 , 0 , 255 , // Black
513
- 128 , 128 , 128 , 255 , // Gray
514
- ]
515
- ) ;
516
-
517
- test_image_to_rgba_pixels ! (
518
- flip_y_rect,
519
- false ,
520
- true ,
521
- Rect {
522
- min: Vec2 :: new( 1.0 , 1.0 ) ,
523
- max: Vec2 :: new( 3.0 , 3.0 )
524
- } ,
525
- [
526
- // Row 3 first: Gray, Black
527
- 128 , 128 , 128 , 255 , // Gray
528
- 0 , 0 , 0 , 255 , // Black
529
- // Row 2 second: Cyan, Magenta
530
- 0 , 255 , 255 , 255 , // Cyan
531
- 255 , 0 , 255 , 255 , // Magenta
532
- ]
533
- ) ;
534
-
535
- test_image_to_rgba_pixels ! (
536
- flip_both_rect,
537
- true ,
538
- true ,
539
- Rect {
540
- min: Vec2 :: new( 1.0 , 1.0 ) ,
541
- max: Vec2 :: new( 3.0 , 3.0 )
542
- } ,
543
- [
544
- // Row 3 flipped: Black, Gray
545
- 0 , 0 , 0 , 255 , // Black
546
- 128 , 128 , 128 , 255 , // Gray
547
- // Row 2 flipped: Magenta, Cyan
548
- 255 , 0 , 255 , 255 , // Magenta
549
- 0 , 255 , 255 , 255 , // Cyan
550
- ]
551
- ) ;
552
- }
0 commit comments