stereokit_rust/sprite.rs
1use crate::{
2 StereoKitError,
3 maths::{Matrix, Vec2},
4 sk::MainThreadToken,
5 system::{IAsset, Pivot},
6 tex::{Tex, TexT},
7 util::Color32,
8};
9use std::{
10 ffi::{CStr, CString, c_char},
11 path::Path,
12 ptr::NonNull,
13};
14
15/// The way the Sprite is stored on the backend! Does it get batched and atlased for draw efficiency, or is it a single image?
16#[derive(Debug, Copy, Clone, PartialEq, Eq)]
17#[repr(u32)]
18pub enum SpriteType {
19 /// The sprite will be batched onto an atlas texture so all sprites can be drawn in a single pass. This is excellent for performance! The only thing to watch out for here, adding a sprite to an atlas will rebuild the atlas texture! This can be a bit expensive, so it’s recommended to add all sprites to an atlas at start, rather than during runtime. Also, if an image is too large, it may take up too much space on the atlas, and may be better as a Single sprite type.
20 Atlased = 0,
21 /// This sprite is on its own texture. This is best for large images, items that get loaded and unloaded during runtime, or for sprites that may have edge artifacts or severe ‘bleed’ from adjacent atlased images.
22 Single = 1,
23}
24
25/// A Sprite is an image that’s set up for direct 2D rendering, without using a mesh or model! This is technically a
26/// wrapper over a texture, but it also includes atlasing functionality, which can be pretty important to performance!
27/// This is used a lot in UI, for image rendering.
28///
29/// Atlasing is not currently implemented, it’ll swap to Single for now. But here’s how it works!
30/// StereoKit will batch your sprites into an atlas if you ask it to! This puts all the images on a single texture to
31/// significantly reduce draw calls when many images are present. Any time you add a sprite to an atlas, it’ll be marked
32/// as dirty and rebuilt at the end of the frame. So it can be a good idea to add all your images to the atlas on
33/// initialize rather than during execution!
34///
35/// Since rendering is atlas based, you also have only one material per atlas. So this is why you might wish to put a
36/// sprite in one atlas or another, so you can apply different
37/// <https://stereokit.net/Pages/StereoKit/Sprite.html>
38///
39/// ### Examples
40/// ```
41/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
42/// use stereokit_rust::{maths::{Vec3, Matrix}, sprite::{Sprite, SpriteType},
43/// tex::Tex, util::{Gradient, Color128, named_colors}, system::Pivot};
44/// let mut gradient = Gradient::new(None);
45/// gradient
46/// .add(Color128::BLACK_TRANSPARENT, 0.0)
47/// .add(named_colors::YELLOW, 0.1)
48/// .add(named_colors::LIGHT_BLUE, 0.4)
49/// .add(named_colors::BLUE, 0.5)
50/// .add(Color128::BLACK, 0.7);
51/// let tex_particule1 = Tex::gen_particle(128, 128, 0.2, Some(gradient));
52/// let tex_particule2 = Tex::gen_particle(128, 128, 0.2, None);
53///
54/// let sprite1 = Sprite::from_tex(&tex_particule1, None, None)
55/// .expect("Should be able to create sprite");
56/// let sprite2 = Sprite::from_file("icons/fly_over.png", Some(SpriteType::Single), Some("MY_ID"))
57/// .expect("open_gltf.jpeg should be able to create sprite");
58/// let sprite3 = sprite1.clone_ref();
59/// let sprite4 = Sprite::from_tex(&tex_particule2, None, None)
60/// .expect("Should be able to create sprite");
61///
62/// let transform1 = Matrix::t([-0.7, 0.4, 0.0]) * Matrix::Y_180;
63/// let transform2 = Matrix::t([ 0.7, 0.4, 0.0]) * Matrix::Y_180;
64/// let transform3 = Matrix::t([-0.7,-0.4, 0.0]) * Matrix::Y_180;
65/// let transform4 = Matrix::t([ 0.7,-0.4, 0.0]) * Matrix::Y_180;
66///
67/// filename_scr = "screenshots/sprite.jpeg";
68/// test_screenshot!( // !!!! Get a proper main loop !!!!
69/// sprite1.draw(token, transform1, Pivot::Center, None);
70/// sprite2.draw(token, transform2, Pivot::XLeft, Some(named_colors::AZURE.into()));
71/// sprite3.draw(token, transform3, Pivot::TopRight, Some(named_colors::LIME.into()));
72/// sprite4.draw(token, transform4, Pivot::YCenter, None);
73/// );
74/// ```
75/// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite.jpeg" alt="screenshot" width="200">
76#[repr(C)]
77#[derive(Debug, PartialEq)]
78pub struct Sprite(pub NonNull<_SpriteT>);
79
80impl Drop for Sprite {
81 fn drop(&mut self) {
82 unsafe { sprite_release(self.0.as_ptr()) };
83 }
84}
85
86impl AsRef<Sprite> for Sprite {
87 fn as_ref(&self) -> &Sprite {
88 self
89 }
90}
91
92/// StereoKit internal type.
93#[repr(C)]
94#[derive(Debug)]
95pub struct _SpriteT {
96 _unused: [u8; 0],
97}
98
99/// StereoKit ffi type.
100pub type SpriteT = *mut _SpriteT;
101
102unsafe extern "C" {
103 pub fn sprite_find(id: *const c_char) -> SpriteT;
104 pub fn sprite_create(sprite: TexT, type_: SpriteType, atlas_id: *const c_char) -> SpriteT;
105 pub fn sprite_create_file(filename_utf8: *const c_char, type_: SpriteType, atlas_id: *const c_char) -> SpriteT;
106
107 pub fn sprite_set_id(sprite: SpriteT, id: *const c_char);
108
109 pub fn sprite_get_id(sprite: SpriteT) -> *const c_char;
110 pub fn sprite_addref(sprite: SpriteT);
111 pub fn sprite_release(sprite: SpriteT);
112 pub fn sprite_get_aspect(sprite: SpriteT) -> f32;
113 pub fn sprite_get_width(sprite: SpriteT) -> i32;
114 pub fn sprite_get_height(sprite: SpriteT) -> i32;
115 pub fn sprite_get_dimensions_normalized(sprite: SpriteT) -> Vec2;
116 pub fn sprite_draw(sprite: SpriteT, transform: Matrix, pivot_position: Pivot, color: Color32);
117}
118
119impl IAsset for Sprite {
120 // fn id(&mut self, id: impl AsRef<str>) {
121 // self.id(id);
122 // }
123
124 fn get_id(&self) -> &str {
125 self.get_id()
126 }
127}
128
129/// A Default sprite is asked when a Sprite creation or find returned an error. (close is the default sprite)
130impl Default for Sprite {
131 fn default() -> Self {
132 Self::close()
133 }
134}
135
136impl Sprite {
137 /// Create a sprite from a texture.
138 /// <https://stereokit.net/Pages/StereoKit/Sprite/FromTex.html>
139 /// * `tex` - The texture to build a sprite from. Must be a valid, 2D image!
140 /// * `sprite_type` - Should this sprite be atlased, or an individual image? Adding this as an atlased image is better for
141 /// performance, but will cause the atlas to be rebuilt! Images that take up too much space on the atlas, or might
142 /// be loaded or unloaded during runtime may be better as Single rather than Atlased!
143 /// If None has default of Atlased.
144 /// * `atlas_id` - The name of which atlas the sprite should belong to, this is only relevant if the SpriteType is
145 /// Atlased. If None has default of "default".
146 ///
147 /// Returns a Sprite asset, or null if the image failed when adding to the sprite system!
148 /// see also [`sprite_create`]
149 /// ### Examples
150 /// ```
151 /// # stereokit_rust::test_init_sk!();
152 /// use stereokit_rust::{ maths::Matrix, sprite::{Sprite, SpriteType}, tex::Tex, system::Pivot,
153 /// util::{Gradient, Color128, named_colors}};
154 ///
155 /// let mut gradient = Gradient::new(None);
156 /// gradient
157 /// .add(Color128::BLACK_TRANSPARENT, 0.0)
158 /// .add(named_colors::YELLOW, 0.1)
159 /// .add(named_colors::LIGHT_BLUE, 0.4)
160 /// .add(named_colors::BLUE, 0.5)
161 /// .add(Color128::BLACK, 0.7);
162 /// let tex_particule1 = Tex::gen_particle(128, 128, 0.2, Some(gradient));
163 /// let sprite = Sprite::from_tex(&tex_particule1, Some(SpriteType::Atlased), Some("my_sprite".to_string()))
164 /// .expect("Should be able to create sprite");
165 ///
166 /// assert!(sprite.get_id().starts_with("auto/tex_"));
167 /// assert_eq!(sprite.get_height(), 128);
168 /// assert_eq!(sprite.get_width(), 128);
169 /// assert_eq!(sprite.get_normalized_dimensions(), [1.0, 1.0].into());
170 /// assert_eq!(sprite.get_aspect(), 1.0);
171 ///
172 /// filename_scr = "screenshots/sprite_from_tex.jpeg";
173 /// test_screenshot!( // !!!! Get a proper main loop !!!!
174 /// sprite.draw(token, Matrix::Y_180, Pivot::XRight, None);
175 /// sprite.draw(token, Matrix::Y_180, Pivot::XLeft, Some(named_colors::BLUE.into()));
176 /// sprite.draw(token, Matrix::Y_180, Pivot::YTop, Some(named_colors::RED.into()));
177 /// sprite.draw(token, Matrix::Y_180, Pivot::YBottom, Some(named_colors::GREEN.into()));
178 /// );
179 /// ```
180 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_from_tex.jpeg" alt="screenshot" width="200">
181 pub fn from_tex(
182 sprite_tex: impl AsRef<Tex>,
183 sprite_type: Option<SpriteType>,
184 atlas_id: Option<String>,
185 ) -> Result<Sprite, StereoKitError> {
186 let sprite_type = sprite_type.unwrap_or(SpriteType::Atlased);
187 let atlas_id = match atlas_id {
188 Some(s) => s,
189 None => "default".to_owned(),
190 };
191 let c_atlas_id = CString::new(atlas_id)?;
192 Ok(Sprite(
193 NonNull::new(unsafe { sprite_create(sprite_tex.as_ref().0.as_ptr(), sprite_type, c_atlas_id.as_ptr()) })
194 .ok_or(StereoKitError::SpriteCreate)?,
195 ))
196 }
197
198 /// Create a sprite from an image file! This loads a Texture from file, and then uses that Texture as the source for the Sprite.
199 /// <https://stereokit.net/Pages/StereoKit/Sprite/FromFile.html>
200 /// * `file_utf8` - The filename of the image, an absolute filename, or a filename relative to the assets folder.
201 /// Supports jpg, png, tga, bmp, psd, gif, hdr, pic.
202 /// * `sprite_type` - Should this sprite be atlased, or an individual image? Adding this as an atlased image is
203 /// better for performance, but will cause the atlas to be rebuilt! Images that take up too much space on the
204 /// atlas, or might be loaded or unloaded during runtime may be better as Single rather than Atlased!
205 /// If None has default of Atlased
206 /// * `atlas_id` - The name of which atlas the sprite should belong to, this is only relevant if the SpriteType is
207 /// Atlased. If None has default of "default".
208 ///
209 /// see also [`sprite_create_file`]
210 /// ### Examples
211 /// ```
212 /// # stereokit_rust::test_init_sk!();
213 /// use stereokit_rust::{ maths::Matrix, sprite::{Sprite, SpriteType}, system::Pivot,
214 /// util::named_colors};
215 ///
216 /// let sprite = Sprite::from_file("icons/log_viewer.png", Some(SpriteType::Single), Some("MY_ID"))
217 /// .expect("open_gltf.jpeg should be able to create sprite");
218 ///
219 /// assert_eq!(sprite.get_id(), "icons/log_viewer.png/sprite");
220 /// assert_eq!(sprite.get_height(), 128);
221 /// assert_eq!(sprite.get_width(), 128);
222 /// assert_eq!(sprite.get_normalized_dimensions().x, 1.0);
223 /// assert_eq!(sprite.get_normalized_dimensions().y, 1.0);
224 ///
225 /// filename_scr = "screenshots/sprite_from_file.jpeg";
226 /// test_screenshot!( // !!!! Get a proper main loop !!!!
227 /// sprite.draw(token, Matrix::Y_180, Pivot::XRight, None);
228 /// sprite.draw(token, Matrix::Y_180, Pivot::XLeft, Some(named_colors::BLUE.into()));
229 /// sprite.draw(token, Matrix::Y_180, Pivot::YTop, Some(named_colors::RED.into()));
230 /// sprite.draw(token, Matrix::Y_180, Pivot::YBottom, Some(named_colors::GREEN.into()));
231 /// );
232 /// ```
233 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_from_file.jpeg" alt="screenshot" width="200">
234 pub fn from_file(
235 file_utf8: impl AsRef<Path>,
236 sprite_type: Option<SpriteType>,
237 atlas_id: Option<&str>,
238 ) -> Result<Sprite, StereoKitError> {
239 let sprite_type = sprite_type.unwrap_or(SpriteType::Atlased);
240 let atlas_id = match atlas_id {
241 Some(s) => s.to_owned(),
242 None => "default".to_owned(),
243 };
244 let c_atlas_id = CString::new(atlas_id)?;
245
246 let path_buf = file_utf8.as_ref().to_path_buf();
247 let c_str = CString::new(path_buf.clone().to_str().ok_or(StereoKitError::SpriteFile(path_buf.clone()))?)?;
248
249 Ok(Sprite(
250 NonNull::new(unsafe { sprite_create_file(c_str.as_ptr(), sprite_type, c_atlas_id.as_ptr()) })
251 .ok_or(StereoKitError::SpriteFile(path_buf))?,
252 ))
253 }
254
255 /// Finds a sprite that matches the given id! Check out the DefaultIds static class for some built-in ids. Sprites
256 /// will auto-id themselves using this pattern if single sprites: {Tex.Id}/sprite, and this pattern if atlased
257 /// sprites: {Tex.Id}/sprite/atlas/{atlasId}.
258 /// <https://stereokit.net/Pages/StereoKit/Sprite/Find.html>
259 /// * `id` - The id of the sprite to find.
260 ///
261 /// see also [`sprite_find`] [`Sprite::clone_ref`]
262 /// ### Examples
263 /// ```
264 /// # stereokit_rust::test_init_sk!();
265 /// use stereokit_rust::{sprite::{Sprite, SpriteType}, tex::Tex,
266 /// util::{Gradient, Color128, named_colors}};
267 ///
268 /// let mut gradient = Gradient::new(None);
269 /// gradient
270 /// .add(Color128::BLACK_TRANSPARENT, 0.0)
271 /// .add(Color128::BLACK, 0.7);
272 /// let tex_particule1 = Tex::gen_particle(128, 128, 0.2, Some(gradient));
273 /// let mut sprite = Sprite::from_tex(&tex_particule1, None, None)
274 /// .expect("Should be able to create sprite");
275 /// assert!(sprite.get_id().starts_with( "auto/tex_"));
276 ///
277 /// sprite.id("My_sprite_ID");
278 ///
279 /// let same_sprite = Sprite::find("My_sprite_ID")
280 /// .expect("Should be able to find sprite");
281 ///
282 /// assert_eq!(same_sprite, sprite);
283 /// ```
284 pub fn find<S: AsRef<str>>(id: S) -> Result<Sprite, StereoKitError> {
285 let cstr_id = CString::new(id.as_ref())?;
286 Ok(Sprite(
287 NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) })
288 .ok_or(StereoKitError::SpriteFind(id.as_ref().to_string(), "sprite_find failed".to_string()))?,
289 ))
290 }
291
292 /// Creates a clone of the same reference. Basically, the new variable is the same asset. This is what you get by
293 /// calling find() method.
294 /// <https://stereokit.net/Pages/StereoKit/Sprite/Find.html>
295 ///
296 /// see also [`sprite_find()`]
297 /// ### Examples
298 /// ```
299 /// # stereokit_rust::test_init_sk!();
300 /// use stereokit_rust::{sprite::{Sprite, SpriteType}, tex::Tex};
301 ///
302 /// let tex = Tex::rough();
303 /// let mut sprite = Sprite::from_tex(&tex, None, None)
304 /// .expect("Should be able to create sprite");
305 /// assert_eq!(sprite.get_id(), "default/tex_rough/sprite");
306 ///
307 /// sprite.id("My_sprite_ID");
308 ///
309 /// let same_sprite = sprite.clone_ref();
310 ///
311 /// assert_eq!(same_sprite, sprite);
312 /// ```
313 pub fn clone_ref(&self) -> Sprite {
314 Sprite(
315 NonNull::new(unsafe { sprite_find(sprite_get_id(self.0.as_ptr())) }).expect("<asset>::clone_ref failed!"),
316 )
317 }
318
319 /// Sets the unique identifier of this asset resource! This can be helpful for debugging,
320 /// managing your assets, or finding them later on!
321 ///<https://stereokit.net/Pages/StereoKit/Sprite/Id.html>
322 ///
323 /// see also [`sprite_get_id`]
324 /// ### Examples
325 /// ```
326 /// # stereokit_rust::test_init_sk!();
327 /// use stereokit_rust::{sprite::{Sprite, SpriteType}, tex::Tex};
328 ///
329 /// let tex = Tex::rough();
330 /// let mut sprite = Sprite::from_tex(&tex, None, None)
331 /// .expect("Should be able to create sprite");
332 /// assert_eq!(sprite.get_id(), "default/tex_rough/sprite");
333 ///
334 /// sprite.id("My_sprite_ID");
335 ///
336 /// assert_eq!(sprite.get_id(), "My_sprite_ID");
337 /// ```
338 pub fn id<S: AsRef<str>>(&mut self, id: S) -> &mut Self {
339 let cstr_id = CString::new(id.as_ref()).unwrap();
340 unsafe { sprite_set_id(self.0.as_ptr(), cstr_id.as_ptr()) };
341 self
342 }
343
344 /// Draws the sprite at the location specified by the transform matrix. A sprite is always sized in model space as 1 x Aspect
345 /// meters on the x and y axes respectively, so scale appropriately. The ‘position’ attribute describes what corner of the sprite
346 /// you’re specifying the transform of.
347 /// <https://stereokit.net/Pages/StereoKit/Sprite/Draw.html>
348 /// * `token` - The token to ensure the sprite is drawn in the correct frame.
349 /// * `transform` - A Matrix describing a transform from model space to world space. A sprite is always sized in
350 /// model space as 1 x Aspect meters on the x and y axes respectively, so scale appropriately and remember that
351 /// your anchor position may affect the transform as well.
352 /// * `pivot_position` - Describes what corner of the sprite you’re specifying the transform of. The ‘Pivot’ point
353 /// or ‘Origin’ of the Sprite.
354 /// * `linear_color` - Per-instance color data for this render item. It is unmodified by StereoKit, and is generally
355 /// interpreted as linear. If None has default value of WHITE.
356 ///
357 /// see also [`sprite_draw`]
358 /// ### Examples
359 /// ```
360 /// # stereokit_rust::test_init_sk!();
361 /// use stereokit_rust::{ maths::Matrix, sprite::{Sprite, SpriteType}, tex::Tex, system::Pivot,
362 /// util::{Gradient, Color128, named_colors}};
363 ///
364 /// let sprite = Sprite::close();
365 ///
366 /// filename_scr = "screenshots/sprite_draw.jpeg";
367 /// test_screenshot!( // !!!! Get a proper main loop !!!!
368 /// sprite.draw(token, Matrix::Y_180, Pivot::TopLeft, None);
369 /// sprite.draw(token, Matrix::Y_180, Pivot::TopRight, Some(named_colors::BLUE.into()));
370 /// sprite.draw(token, Matrix::Y_180, Pivot::BottomLeft, Some(named_colors::RED.into()));
371 /// sprite.draw(token, Matrix::Y_180, Pivot::BottomRight, Some(named_colors::GREEN.into()));
372 /// );
373 /// ```
374 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_draw.jpeg" alt="screenshot" width="200">
375 pub fn draw(
376 &self,
377 _token: &MainThreadToken,
378 transform: impl Into<Matrix>,
379 pivot_position: Pivot,
380 linear_color: Option<Color32>,
381 ) {
382 let color_linear = linear_color.unwrap_or(Color32::WHITE);
383 unsafe { sprite_draw(self.0.as_ptr(), transform.into(), pivot_position, color_linear) };
384 }
385
386 /// The id of this sprite
387 /// <https://stereokit.net/Pages/StereoKit/Sprite/Id.html>
388 ///
389 /// see also [`sprite_get_id`]
390 /// see example in [`Sprite::id`]
391 pub fn get_id(&self) -> &str {
392 unsafe { CStr::from_ptr(sprite_get_id(self.0.as_ptr())) }.to_str().unwrap()
393 }
394
395 /// The aspect ratio of the sprite! This is width/height. You may also be interested in the NormalizedDimensions property,
396 /// which are normalized to the 0-1 range.
397 /// <https://stereokit.net/Pages/StereoKit/Sprite/Aspect.html>
398 ///
399 /// see also [`sprite_get_aspect`]
400 /// ### Examples
401 /// ```
402 /// # stereokit_rust::test_init_sk!();
403 /// use stereokit_rust::sprite::{Sprite, SpriteType};
404 ///
405 /// let sprite = Sprite::from_file("icons/log_viewer.png", Some(SpriteType::Single), Some("MY_ID"))
406 /// .expect("open_gltf.jpeg should be able to create sprite");
407 /// assert_eq!(sprite.get_aspect(), 1.0);
408 ///
409 /// let sprite = Sprite::from_file("hdri/sky_dawn.hdr", None, None)
410 /// .expect("open_gltf.jpeg should be able to create sprite");
411 /// assert_eq!(sprite.get_aspect(), 2.0);
412 /// ```
413 pub fn get_aspect(&self) -> f32 {
414 unsafe { sprite_get_aspect(self.0.as_ptr()) }
415 }
416
417 /// Get the height in pixel
418 /// <https://stereokit.net/Pages/StereoKit/Sprite/Height.html>
419 ///
420 /// see also [`sprite_get_height`]
421 /// ### Examples
422 /// ```
423 /// # stereokit_rust::test_init_sk!();
424 /// use stereokit_rust::sprite::{Sprite, SpriteType};
425 ///
426 /// let sprite = Sprite::from_file("icons/log_viewer.png", Some(SpriteType::Single), Some("MY_ID"))
427 /// .expect("open_gltf.jpeg should be able to create sprite");
428 /// assert_eq!(sprite.get_height(), 128);
429 ///
430 /// let sprite = Sprite::from_file("hdri/sky_dawn.hdr", None, None)
431 /// .expect("open_gltf.jpeg should be able to create sprite");
432 /// assert_eq!(sprite.get_height(), 2048);
433 /// ```
434 pub fn get_height(&self) -> i32 {
435 unsafe { sprite_get_height(self.0.as_ptr()) }
436 }
437
438 /// Get the width in pixel
439 /// <https://stereokit.net/Pages/StereoKit/Sprite/Width.html>
440 ///
441 /// see also [`sprite_get_width`]
442 /// ### Examples
443 /// ```
444 /// # stereokit_rust::test_init_sk!();
445 /// use stereokit_rust::sprite::{Sprite, SpriteType};
446 ///
447 /// let sprite = Sprite::from_file("icons/log_viewer.png", Some(SpriteType::Single), Some("MY_ID"))
448 /// .expect("open_gltf.jpeg should be able to create sprite");
449 /// assert_eq!(sprite.get_width(), 128);
450 ///
451 /// let sprite = Sprite::from_file("hdri/sky_dawn.hdr", None, None)
452 /// .expect("open_gltf.jpeg should be able to create sprite");
453 /// assert_eq!(sprite.get_width(), 4096);
454 /// ```
455 pub fn get_width(&self) -> i32 {
456 unsafe { sprite_get_width(self.0.as_ptr()) }
457 }
458
459 /// Get the width and height of the sprite, normalized so the maximum value is 1.
460 /// <https://stereokit.net/Pages/StereoKit/Sprite/NormalizedDimensions.html>
461 ///
462 /// see also [`sprite_get_dimensions_normalized`]
463 /// ### Examples
464 /// ```
465 /// # stereokit_rust::test_init_sk!();
466 /// use stereokit_rust::{sprite::{Sprite, SpriteType}, maths::Vec2};
467 ///
468 /// let sprite = Sprite::from_file("icons/log_viewer.png", Some(SpriteType::Single), Some("MY_ID"))
469 /// .expect("open_gltf.jpeg should be able to create sprite");
470 /// assert_eq!(sprite.get_normalized_dimensions(), Vec2::ONE);
471 ///
472 /// let sprite = Sprite::from_file("hdri/sky_dawn.hdr", None, None)
473 /// .expect("open_gltf.jpeg should be able to create sprite");
474 /// assert_eq!(sprite.get_normalized_dimensions(), Vec2::new(1.0, 0.5));
475 /// ```
476 pub fn get_normalized_dimensions(&self) -> Vec2 {
477 unsafe { sprite_get_dimensions_normalized(self.0.as_ptr()) }
478 }
479
480 /// This is a 64x64 image of a filled hole. This is common iconography for radio buttons which use an empty hole to
481 /// indicate an un-selected radio, and a filled hole for a selected radio. This is used by the UI for radio buttons!
482 /// <https://stereokit.net/Pages/StereoKit/Sprite/RadioOn.html>
483 ///
484 /// ### Examples
485 /// ```
486 /// # stereokit_rust::test_init_sk!();
487 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
488 ///
489 /// let sprite = Sprite::radio_on();
490 /// assert_eq!(sprite.get_id(), "sk/ui/radio_on");
491 ///
492 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
493 /// filename_scr = "screenshots/sprite_radio_on.jpeg";
494 /// test_screenshot!( // !!!! Get a proper main loop !!!!
495 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
496 /// );
497 /// ```
498 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_radio_on.jpeg" alt="screenshot" width="48">
499 pub fn radio_on() -> Self {
500 let cstr_id = CString::new("sk/ui/radio_on").unwrap();
501 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
502 }
503
504 /// This is a 64x64 image of an empty hole. This is common iconography for radio buttons which use an empty hole to
505 /// indicate an un-selected radio, and a filled hole for a selected radio. This is used by the UI for radio buttons!
506 /// <https://stereokit.net/Pages/StereoKit/Sprite/RadioOff.html>
507 ///
508 /// ### Examples
509 /// ```
510 /// # stereokit_rust::test_init_sk!();
511 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
512 ///
513 /// let sprite = Sprite::radio_off();
514 /// assert_eq!(sprite.get_id(), "sk/ui/radio_off");
515 ///
516 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
517 /// filename_scr = "screenshots/sprite_radio_off.jpeg";
518 /// test_screenshot!( // !!!! Get a proper main loop !!!!
519 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
520 /// );
521 /// ```
522 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_radio_off.jpeg" alt="screenshot" width="48">
523 pub fn radio_off() -> Self {
524 let cstr_id = CString::new("sk/ui/radio_off").unwrap();
525 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
526 }
527
528 /// This is a 64x64 image of a filled rounded square. This is common iconography for checkboxes which use an
529 /// empty square to indicate an un-selected checkbox, and a filled square for a selected checkbox. This is used
530 /// by the UI for toggle buttons!
531 /// <https://stereokit.net/Pages/StereoKit/Sprite/ToggleOn.html>
532 ///
533 /// ### Examples
534 /// ```
535 /// # stereokit_rust::test_init_sk!();
536 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
537 ///
538 /// let sprite = Sprite::toggle_on();
539 /// assert_eq!(sprite.get_id(), "sk/ui/toggle_on");
540 ///
541 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
542 /// filename_scr = "screenshots/sprite_toggle_on.jpeg";
543 /// test_screenshot!( // !!!! Get a proper main loop !!!!
544 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
545 /// );
546 /// ```
547 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_toggle_on.jpeg" alt="screenshot" width="48">
548 pub fn toggle_on() -> Self {
549 let cstr_id = CString::new("sk/ui/toggle_on").unwrap();
550 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
551 }
552
553 /// This is a 64x64 image of an empty rounded square. This is common iconography for checkboxes which use an empty
554 /// square to indicate an un-selected checkbox, and a filled square for a selected checkbox. This is used by the UI
555 /// for toggle buttons!
556 /// <https://stereokit.net/Pages/StereoKit/Sprite/ToggleOff.html>
557 ///
558 /// ### Examples
559 /// ```
560 /// # stereokit_rust::test_init_sk!();
561 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
562 ///
563 /// let sprite = Sprite::toggle_off();
564 /// assert_eq!(sprite.get_id(), "sk/ui/toggle_off");
565 ///
566 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
567 /// filename_scr = "screenshots/sprite_toggle_off.jpeg";
568 /// test_screenshot!( // !!!! Get a proper main loop !!!!
569 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
570 /// );
571 /// ```
572 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_toggle_off.jpeg" alt="screenshot" width="48">
573 pub fn toggle_off() -> Self {
574 let cstr_id = CString::new("sk/ui/toggle_off").unwrap();
575 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
576 }
577
578 /// This is a 64x64 image of a slightly rounded triangle pointing left.
579 /// <https://stereokit.net/Pages/StereoKit/Sprite/ArrowLeft.html>
580 ///
581 /// ### Examples
582 /// ```
583 /// # stereokit_rust::test_init_sk!();
584 /// use stereokit_rust::{sprite::Sprite, maths::{Vec3, Matrix}, system::Pivot};
585 ///
586 /// let sprite = Sprite::arrow_left();
587 /// assert_eq!(sprite.get_id(), "sk/ui/arrow_left");
588 ///
589 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
590 /// filename_scr = "screenshots/sprite_arrow_left.jpeg";
591 /// test_screenshot!( // !!!! Get a proper main loop !!!!
592 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
593 /// );
594 /// ```
595 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_arrow_left.jpeg" alt="screenshot" width="48">
596 pub fn arrow_left() -> Self {
597 let cstr_id = CString::new("sk/ui/arrow_left").unwrap();
598 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
599 }
600
601 /// This is a 64x64 image of a slightly rounded triangle pointing right.
602 /// <https://stereokit.net/Pages/StereoKit/Sprite/ArrowRight.html>
603 ///
604 /// ### Examples
605 /// ```
606 /// # stereokit_rust::test_init_sk!();
607 /// use stereokit_rust::{sprite::Sprite, maths::{Vec3, Matrix}, system::Pivot};
608 ///
609 /// let sprite = Sprite::arrow_right();
610 /// assert_eq!(sprite.get_id(), "sk/ui/arrow_right");
611 ///
612 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
613 /// filename_scr = "screenshots/sprite_arrow_right.jpeg";
614 /// test_screenshot!( // !!!! Get a proper main loop !!!!
615 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
616 /// );
617 /// ```
618 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_arrow_right.jpeg" alt="screenshot" width="48">
619 pub fn arrow_right() -> Self {
620 let cstr_id = CString::new("sk/ui/arrow_right").unwrap();
621 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
622 }
623
624 /// This is a 64x64 image of a slightly rounded triangle pointing up.
625 /// <https://stereokit.net/Pages/StereoKit/Sprite/ArrowUp.html>
626 ///
627 /// ### Examples
628 /// ```
629 /// # stereokit_rust::test_init_sk!();
630 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
631 ///
632 /// let sprite = Sprite::arrow_up();
633 /// assert_eq!(sprite.get_id(), "sk/ui/arrow_up");
634 ///
635 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
636 /// filename_scr = "screenshots/sprite_arrow_up.jpeg";
637 /// test_screenshot!( // !!!! Get a proper main loop !!!!
638 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
639 /// );
640 /// ```
641 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_arrow_up.jpeg" alt="screenshot" width="48">
642 pub fn arrow_up() -> Self {
643 let cstr_id = CString::new("sk/ui/arrow_up").unwrap();
644 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
645 }
646
647 /// This is a 64x64 image of a slightly rounded triangle pointing down.
648 /// <https://stereokit.net/Pages/StereoKit/Sprite/ArrowDown.html>
649 ///
650 /// ### Examples
651 /// ```
652 /// # stereokit_rust::test_init_sk!();
653 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
654 ///
655 /// let sprite = Sprite::arrow_down();
656 /// assert_eq!(sprite.get_id(), "sk/ui/arrow_down");
657 ///
658 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
659 /// filename_scr = "screenshots/sprite_arrow_down.jpeg";
660 /// test_screenshot!( // !!!! Get a proper main loop !!!!
661 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
662 /// );
663 /// ```
664 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_arrow_down.jpeg" alt="screenshot" width="48">
665 pub fn arrow_down() -> Self {
666 let cstr_id = CString::new("sk/ui/arrow_down").unwrap();
667 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
668 }
669
670 /// This is a 64x64 image of a backspace action button, similar to a backspace button you might find on a mobile
671 /// keyboard.
672 /// <https://stereokit.net/Pages/StereoKit/Sprite/Backspace.html>
673 ///
674 /// ### Examples
675 /// ```
676 /// # stereokit_rust::test_init_sk!();
677 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
678 ///
679 /// let sprite = Sprite::backspace();
680 /// assert_eq!(sprite.get_id(), "sk/ui/backspace");
681 ///
682 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
683 /// filename_scr = "screenshots/sprite_backspace.jpeg";
684 /// test_screenshot!( // !!!! Get a proper main loop !!!!
685 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
686 /// );
687 /// ```
688 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_backspace.jpeg" alt="screenshot" width="48">
689 pub fn backspace() -> Self {
690 let cstr_id = CString::new("sk/ui/backspace").unwrap();
691 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
692 }
693
694 /// This is a 64x64 image of an upward facing rounded arrow. This is a triangular top with a narrow rectangular
695 /// base, and is used to indicate a ‘shift’ icon on a keyboard.
696 /// <https://stereokit.net/Pages/StereoKit/Sprite/Shift.html>
697 ///
698 /// ### Examples
699 /// ```
700 /// # stereokit_rust::test_init_sk!();
701 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
702 ///
703 /// let sprite = Sprite::shift();
704 /// assert_eq!(sprite.get_id(), "sk/ui/shift");
705 ///
706 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
707 /// filename_scr = "screenshots/sprite_shift.jpeg";
708 /// test_screenshot!( // !!!! Get a proper main loop !!!!
709 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
710 /// );
711 /// ```
712 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_shift.jpeg" alt="screenshot" width="48">
713 pub fn shift() -> Self {
714 let cstr_id = CString::new("sk/ui/shift").unwrap();
715 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
716 }
717
718 /// This is a 64x64 image of a square aspect X, with rounded edge. It’s used to indicate a ‘close’ icon.
719 /// <https://stereokit.net/Pages/StereoKit/Sprite/Close.html>
720 ///
721 /// ### Examples
722 /// ```
723 /// # stereokit_rust::test_init_sk!();
724 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
725 ///
726 /// let sprite = Sprite::close();
727 /// assert_eq!(sprite.get_id(), "sk/ui/close");
728 ///
729 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
730 /// filename_scr = "screenshots/sprite_close.jpeg";
731 /// test_screenshot!( // !!!! Get a proper main loop !!!!
732 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
733 /// );
734 /// ```
735 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_close.jpeg" alt="screenshot" width="48">
736 pub fn close() -> Self {
737 let cstr_id = CString::new("sk/ui/close").unwrap();
738 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
739 }
740
741 /// <https://stereokit.net/Pages/StereoKit/Sprite/List.html>
742 ///
743 /// ### Examples
744 /// ```
745 /// # stereokit_rust::test_init_sk!();
746 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
747 ///
748 /// let sprite = Sprite::list();
749 /// assert_eq!(sprite.get_id(), "sk/ui/list");
750 ///
751 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
752 /// filename_scr = "screenshots/sprite_list.jpeg";
753 /// test_screenshot!( // !!!! Get a proper main loop !!!!
754 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
755 /// );
756 /// ```
757 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_list.jpeg" alt="screenshot" width="48">
758 pub fn list() -> Self {
759 let cstr_id = CString::new("sk/ui/list").unwrap();
760 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
761 }
762
763 /// <https://stereokit.net/Pages/StereoKit/Sprite/Grid.html>
764 ///
765 /// ### Examples
766 /// ```
767 /// # stereokit_rust::test_init_sk!();
768 /// use stereokit_rust::{sprite::Sprite, maths::Matrix, system::Pivot};
769 ///
770 /// let sprite = Sprite::grid();
771 /// assert_eq!(sprite.get_id(), "sk/ui/grid");
772 ///
773 /// width_scr = 48; height_scr = 48; fov_scr = 65.0;
774 /// filename_scr = "screenshots/sprite_grid.jpeg";
775 /// test_screenshot!( // !!!! Get a proper main loop !!!!
776 /// sprite.draw(token, Matrix::Y_180, Pivot::Center, None);
777 /// );
778 /// ```
779 /// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/sprite_grid.jpeg" alt="screenshot" width="48">
780 pub fn grid() -> Self {
781 let cstr_id = CString::new("sk/ui/grid").unwrap();
782 Sprite(NonNull::new(unsafe { sprite_find(cstr_id.as_ptr()) }).unwrap())
783 }
784}