Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

605
vendor/bevy_winit/src/custom_cursor.rs vendored Normal file
View File

@@ -0,0 +1,605 @@
use bevy_app::{App, Plugin};
use bevy_asset::{Assets, Handle};
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout, TextureAtlasPlugin};
use bevy_math::{ops, Rect, URect, UVec2, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use wgpu_types::TextureFormat;
use crate::{cursor::CursorIcon, state::CustomCursorCache};
/// A custom cursor created from an image.
#[derive(Debug, Clone, Default, Reflect, PartialEq, Eq, Hash)]
#[reflect(Debug, Default, Hash, PartialEq, Clone)]
pub struct CustomCursorImage {
/// Handle to the image to use as the cursor. The image must be in 8 bit int
/// or 32 bit float rgba. PNG images work well for this.
pub handle: Handle<Image>,
/// An optional texture atlas used to render the image.
pub texture_atlas: Option<TextureAtlas>,
/// Whether the image should be flipped along its x-axis.
///
/// If true, the cursor's `hotspot` automatically flips along with the
/// image.
pub flip_x: bool,
/// Whether the image should be flipped along its y-axis.
///
/// If true, the cursor's `hotspot` automatically flips along with the
/// image.
pub flip_y: bool,
/// An optional rectangle representing the region of the image to render,
/// instead of rendering the full image. This is an easy one-off alternative
/// to using a [`TextureAtlas`].
///
/// When used with a [`TextureAtlas`], the rect is offset by the atlas's
/// minimal (top-left) corner position.
pub rect: Option<URect>,
/// X and Y coordinates of the hotspot in pixels. The hotspot must be within
/// the image bounds.
///
/// If you are flipping the image using `flip_x` or `flip_y`, you don't need
/// to adjust this field to account for the flip because it is adjusted
/// automatically.
pub hotspot: (u16, u16),
}
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
/// A custom cursor created from a URL.
#[derive(Debug, Clone, Default, Reflect, PartialEq, Eq, Hash)]
#[reflect(Debug, Default, Hash, PartialEq, Clone)]
pub struct CustomCursorUrl {
/// Web URL to an image to use as the cursor. PNGs are preferred. Cursor
/// creation can fail if the image is invalid or not reachable.
pub url: String,
/// X and Y coordinates of the hotspot in pixels. The hotspot must be within
/// the image bounds.
pub hotspot: (u16, u16),
}
/// Custom cursor image data.
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash)]
#[reflect(Clone, PartialEq, Hash)]
pub enum CustomCursor {
/// Use an image as the cursor.
Image(CustomCursorImage),
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
/// Use a URL to an image as the cursor.
Url(CustomCursorUrl),
}
impl From<CustomCursor> for CursorIcon {
fn from(cursor: CustomCursor) -> Self {
CursorIcon::Custom(cursor)
}
}
/// Adds support for custom cursors.
pub(crate) struct CustomCursorPlugin;
impl Plugin for CustomCursorPlugin {
fn build(&self, app: &mut App) {
if !app.is_plugin_added::<TextureAtlasPlugin>() {
app.add_plugins(TextureAtlasPlugin);
}
app.init_resource::<CustomCursorCache>();
}
}
/// Determines the effective rect and returns it along with a flag to indicate
/// whether a sub-image operation is needed. The flag allows the caller to
/// determine whether the image data needs a sub-image extracted from it. Note:
/// To avoid lossy comparisons between [`Rect`] and [`URect`], the flag is
/// always set to `true` when a [`TextureAtlas`] is used.
#[inline(always)]
pub(crate) fn calculate_effective_rect(
texture_atlas_layouts: &Assets<TextureAtlasLayout>,
image: &Image,
texture_atlas: &Option<TextureAtlas>,
rect: &Option<URect>,
) -> (Rect, bool) {
let atlas_rect = texture_atlas
.as_ref()
.and_then(|s| s.texture_rect(texture_atlas_layouts))
.map(|r| r.as_rect());
match (atlas_rect, rect) {
(None, None) => (
Rect {
min: Vec2::ZERO,
max: Vec2::new(
image.texture_descriptor.size.width as f32,
image.texture_descriptor.size.height as f32,
),
},
false,
),
(None, Some(image_rect)) => (
image_rect.as_rect(),
image_rect
!= &URect {
min: UVec2::ZERO,
max: UVec2::new(
image.texture_descriptor.size.width,
image.texture_descriptor.size.height,
),
},
),
(Some(atlas_rect), None) => (atlas_rect, true),
(Some(atlas_rect), Some(image_rect)) => (
{
let mut image_rect = image_rect.as_rect();
image_rect.min += atlas_rect.min;
image_rect.max += atlas_rect.min;
image_rect
},
true,
),
}
}
/// Extracts the RGBA pixel data from `image`, converting it if necessary.
///
/// Only supports rgba8 and rgba32float formats.
pub(crate) fn extract_rgba_pixels(image: &Image) -> Option<Vec<u8>> {
match image.texture_descriptor.format {
TextureFormat::Rgba8Unorm
| TextureFormat::Rgba8UnormSrgb
| TextureFormat::Rgba8Snorm
| TextureFormat::Rgba8Uint
| TextureFormat::Rgba8Sint => Some(image.data.clone()?),
TextureFormat::Rgba32Float => image.data.as_ref().map(|data| {
data.chunks(4)
.map(|chunk| {
let chunk = chunk.try_into().unwrap();
let num = bytemuck::cast_ref::<[u8; 4], f32>(chunk);
ops::round(num.clamp(0.0, 1.0) * 255.0) as u8
})
.collect()
}),
_ => None,
}
}
/// Returns the `image` data as a `Vec<u8>` for the specified sub-region.
///
/// The image is flipped along the x and y axes if `flip_x` and `flip_y` are
/// `true`, respectively.
///
/// Only supports rgba8 and rgba32float formats.
pub(crate) fn extract_and_transform_rgba_pixels(
image: &Image,
flip_x: bool,
flip_y: bool,
rect: Rect,
) -> Option<Vec<u8>> {
let image_data = extract_rgba_pixels(image)?;
let width = rect.width() as usize;
let height = rect.height() as usize;
let mut sub_image_data = Vec::with_capacity(width * height * 4); // assuming 4 bytes per pixel (RGBA8)
for y in 0..height {
for x in 0..width {
let src_x = if flip_x { width - 1 - x } else { x };
let src_y = if flip_y { height - 1 - y } else { y };
let index = ((rect.min.y as usize + src_y)
* image.texture_descriptor.size.width as usize
+ (rect.min.x as usize + src_x))
* 4;
sub_image_data.extend_from_slice(&image_data[index..index + 4]);
}
}
Some(sub_image_data)
}
/// Transforms the `hotspot` coordinates based on whether the image is flipped
/// or not. The `rect` is used to determine the image's dimensions.
pub(crate) fn transform_hotspot(
hotspot: (u16, u16),
flip_x: bool,
flip_y: bool,
rect: Rect,
) -> (u16, u16) {
let hotspot_x = hotspot.0 as f32;
let hotspot_y = hotspot.1 as f32;
let (width, height) = (rect.width(), rect.height());
let hotspot_x = if flip_x {
(width - 1.0).max(0.0) - hotspot_x
} else {
hotspot_x
};
let hotspot_y = if flip_y {
(height - 1.0).max(0.0) - hotspot_y
} else {
hotspot_y
};
(hotspot_x as u16, hotspot_y as u16)
}
#[cfg(test)]
mod tests {
use bevy_app::App;
use bevy_asset::RenderAssetUsages;
use bevy_image::Image;
use bevy_math::Rect;
use bevy_math::Vec2;
use wgpu_types::{Extent3d, TextureDimension};
use super::*;
fn create_image_rgba8(data: &[u8]) -> Image {
Image::new(
Extent3d {
width: 3,
height: 3,
depth_or_array_layers: 1,
},
TextureDimension::D2,
data.to_vec(),
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::default(),
)
}
macro_rules! test_calculate_effective_rect {
($name:ident, $use_texture_atlas:expr, $rect:expr, $expected_rect:expr, $expected_needs_sub_image:expr) => {
#[test]
fn $name() {
let mut app = App::new();
let mut texture_atlas_layout_assets = Assets::<TextureAtlasLayout>::default();
// Create a simple 3x3 texture atlas layout for the test cases
// that use a texture atlas. In the future we could adjust the
// test cases to use different texture atlas layouts.
let layout = TextureAtlasLayout::from_grid(UVec2::new(3, 3), 1, 1, None, None);
let layout_handle = texture_atlas_layout_assets.add(layout);
app.insert_resource(texture_atlas_layout_assets);
let texture_atlases = app
.world()
.get_resource::<Assets<TextureAtlasLayout>>()
.unwrap();
let image = create_image_rgba8(&[0; 3 * 3 * 4]); // 3x3 image
let texture_atlas = if $use_texture_atlas {
Some(TextureAtlas::from(layout_handle))
} else {
None
};
let rect = $rect;
let (result_rect, needs_sub_image) =
calculate_effective_rect(&texture_atlases, &image, &texture_atlas, &rect);
assert_eq!(result_rect, $expected_rect);
assert_eq!(needs_sub_image, $expected_needs_sub_image);
}
};
}
test_calculate_effective_rect!(
no_texture_atlas_no_rect,
false,
None,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
false
);
test_calculate_effective_rect!(
no_texture_atlas_with_partial_rect,
false,
Some(URect {
min: UVec2::new(1, 1),
max: UVec2::new(3, 3)
}),
Rect {
min: Vec2::new(1.0, 1.0),
max: Vec2::new(3.0, 3.0)
},
true
);
test_calculate_effective_rect!(
no_texture_atlas_with_full_rect,
false,
Some(URect {
min: UVec2::ZERO,
max: UVec2::new(3, 3)
}),
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
false
);
test_calculate_effective_rect!(
texture_atlas_no_rect,
true,
None,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
true // always needs sub-image to avoid comparing Rect against URect
);
test_calculate_effective_rect!(
texture_atlas_rect,
true,
Some(URect {
min: UVec2::ZERO,
max: UVec2::new(3, 3)
}),
Rect {
min: Vec2::new(0.0, 0.0),
max: Vec2::new(3.0, 3.0)
},
true // always needs sub-image to avoid comparing Rect against URect
);
fn create_image_rgba32float(data: &[u8]) -> Image {
let float_data: Vec<f32> = data
.chunks(4)
.flat_map(|chunk| {
chunk
.iter()
.map(|&x| x as f32 / 255.0) // convert each channel to f32
.collect::<Vec<f32>>()
})
.collect();
Image::new(
Extent3d {
width: 3,
height: 3,
depth_or_array_layers: 1,
},
TextureDimension::D2,
bytemuck::cast_slice(&float_data).to_vec(),
TextureFormat::Rgba32Float,
RenderAssetUsages::default(),
)
}
macro_rules! test_extract_and_transform_rgba_pixels {
($name:ident, $flip_x:expr, $flip_y:expr, $rect:expr, $expected:expr) => {
#[test]
fn $name() {
let image_data: &[u8] = &[
// Row 1: Red, Green, Blue
255, 0, 0, 255, // Red
0, 255, 0, 255, // Green
0, 0, 255, 255, // Blue
// Row 2: Yellow, Cyan, Magenta
255, 255, 0, 255, // Yellow
0, 255, 255, 255, // Cyan
255, 0, 255, 255, // Magenta
// Row 3: White, Gray, Black
255, 255, 255, 255, // White
128, 128, 128, 255, // Gray
0, 0, 0, 255, // Black
];
// RGBA8 test
{
let image = create_image_rgba8(image_data);
let rect = $rect;
let result = extract_and_transform_rgba_pixels(&image, $flip_x, $flip_y, rect);
assert_eq!(result, Some($expected.to_vec()));
}
// RGBA32Float test
{
let image = create_image_rgba32float(image_data);
let rect = $rect;
let result = extract_and_transform_rgba_pixels(&image, $flip_x, $flip_y, rect);
assert_eq!(result, Some($expected.to_vec()));
}
}
};
}
test_extract_and_transform_rgba_pixels!(
no_flip_full_image,
false,
false,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
[
// Row 1: Red, Green, Blue
255, 0, 0, 255, // Red
0, 255, 0, 255, // Green
0, 0, 255, 255, // Blue
// Row 2: Yellow, Cyan, Magenta
255, 255, 0, 255, // Yellow
0, 255, 255, 255, // Cyan
255, 0, 255, 255, // Magenta
// Row 3: White, Gray, Black
255, 255, 255, 255, // White
128, 128, 128, 255, // Gray
0, 0, 0, 255, // Black
]
);
test_extract_and_transform_rgba_pixels!(
flip_x_full_image,
true,
false,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
[
// Row 1 flipped: Blue, Green, Red
0, 0, 255, 255, // Blue
0, 255, 0, 255, // Green
255, 0, 0, 255, // Red
// Row 2 flipped: Magenta, Cyan, Yellow
255, 0, 255, 255, // Magenta
0, 255, 255, 255, // Cyan
255, 255, 0, 255, // Yellow
// Row 3 flipped: Black, Gray, White
0, 0, 0, 255, // Black
128, 128, 128, 255, // Gray
255, 255, 255, 255, // White
]
);
test_extract_and_transform_rgba_pixels!(
flip_y_full_image,
false,
true,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
[
// Row 3: White, Gray, Black
255, 255, 255, 255, // White
128, 128, 128, 255, // Gray
0, 0, 0, 255, // Black
// Row 2: Yellow, Cyan, Magenta
255, 255, 0, 255, // Yellow
0, 255, 255, 255, // Cyan
255, 0, 255, 255, // Magenta
// Row 1: Red, Green, Blue
255, 0, 0, 255, // Red
0, 255, 0, 255, // Green
0, 0, 255, 255, // Blue
]
);
test_extract_and_transform_rgba_pixels!(
flip_both_full_image,
true,
true,
Rect {
min: Vec2::ZERO,
max: Vec2::new(3.0, 3.0)
},
[
// Row 3 flipped: Black, Gray, White
0, 0, 0, 255, // Black
128, 128, 128, 255, // Gray
255, 255, 255, 255, // White
// Row 2 flipped: Magenta, Cyan, Yellow
255, 0, 255, 255, // Magenta
0, 255, 255, 255, // Cyan
255, 255, 0, 255, // Yellow
// Row 1 flipped: Blue, Green, Red
0, 0, 255, 255, // Blue
0, 255, 0, 255, // Green
255, 0, 0, 255, // Red
]
);
test_extract_and_transform_rgba_pixels!(
no_flip_rect,
false,
false,
Rect {
min: Vec2::new(1.0, 1.0),
max: Vec2::new(3.0, 3.0)
},
[
// Only includes part of the original image (sub-rectangle)
// Row 2, columns 2-3: Cyan, Magenta
0, 255, 255, 255, // Cyan
255, 0, 255, 255, // Magenta
// Row 3, columns 2-3: Gray, Black
128, 128, 128, 255, // Gray
0, 0, 0, 255, // Black
]
);
test_extract_and_transform_rgba_pixels!(
flip_x_rect,
true,
false,
Rect {
min: Vec2::new(1.0, 1.0),
max: Vec2::new(3.0, 3.0)
},
[
// Row 2 flipped: Magenta, Cyan
255, 0, 255, 255, // Magenta
0, 255, 255, 255, // Cyan
// Row 3 flipped: Black, Gray
0, 0, 0, 255, // Black
128, 128, 128, 255, // Gray
]
);
test_extract_and_transform_rgba_pixels!(
flip_y_rect,
false,
true,
Rect {
min: Vec2::new(1.0, 1.0),
max: Vec2::new(3.0, 3.0)
},
[
// Row 3 first: Gray, Black
128, 128, 128, 255, // Gray
0, 0, 0, 255, // Black
// Row 2 second: Cyan, Magenta
0, 255, 255, 255, // Cyan
255, 0, 255, 255, // Magenta
]
);
test_extract_and_transform_rgba_pixels!(
flip_both_rect,
true,
true,
Rect {
min: Vec2::new(1.0, 1.0),
max: Vec2::new(3.0, 3.0)
},
[
// Row 3 flipped: Black, Gray
0, 0, 0, 255, // Black
128, 128, 128, 255, // Gray
// Row 2 flipped: Magenta, Cyan
255, 0, 255, 255, // Magenta
0, 255, 255, 255, // Cyan
]
);
#[test]
fn test_transform_hotspot() {
fn test(hotspot: (u16, u16), flip_x: bool, flip_y: bool, rect: Rect, expected: (u16, u16)) {
let transformed = transform_hotspot(hotspot, flip_x, flip_y, rect);
assert_eq!(transformed, expected);
// Round-trip test: Applying the same transformation again should
// reverse it.
let transformed = transform_hotspot(transformed, flip_x, flip_y, rect);
assert_eq!(transformed, hotspot);
}
let rect = Rect {
min: Vec2::ZERO,
max: Vec2::new(100.0, 200.0),
};
test((10, 20), false, false, rect, (10, 20)); // no flip
test((10, 20), true, false, rect, (89, 20)); // flip X
test((10, 20), false, true, rect, (10, 179)); // flip Y
test((10, 20), true, true, rect, (89, 179)); // flip both
test((0, 0), true, true, rect, (99, 199)); // flip both (bounds check)
}
}