├── .gitignore
├── examples
├── trivial_cli_demo
│ ├── .gitignore
│ ├── thumbnail.png
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ └── main.rs
│ └── Cargo.lock
└── trivial_pixels_demo
│ ├── .gitignore
│ ├── thumbnail.png
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ └── main.rs
│ └── Cargo.lock
├── CHANGELOG.md
├── LICENSE
├── Cargo.toml
├── src
└── pixel_loop
│ ├── tao.rs
│ ├── canvas
│ ├── in_memory.rs
│ ├── pixels.rs
│ ├── mod.rs
│ └── crossterm.rs
│ ├── input
│ ├── mod.rs
│ ├── pixels.rs
│ └── crossterm.rs
│ ├── color.rs
│ └── lib.rs
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/examples/trivial_cli_demo/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/examples/trivial_pixels_demo/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/examples/trivial_cli_demo/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakobwesthoff/pixel_loop/HEAD/examples/trivial_cli_demo/thumbnail.png
--------------------------------------------------------------------------------
/examples/trivial_pixels_demo/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakobwesthoff/pixel_loop/HEAD/examples/trivial_pixels_demo/thumbnail.png
--------------------------------------------------------------------------------
/examples/trivial_cli_demo/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "trivial_cli_demo"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | pixel_loop = { version = "*", path = "../../", default-features = false, features = [
8 | "crossterm",
9 | ] }
10 | anyhow = "1.0.92"
11 |
--------------------------------------------------------------------------------
/examples/trivial_pixels_demo/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "trivial_pixels_demo"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | anyhow = "*"
8 | pixel_loop = { version = "*", path = "../../", default-features = false, features = [
9 | "pixels",
10 | ] }
11 |
--------------------------------------------------------------------------------
/examples/trivial_pixels_demo/README.md:
--------------------------------------------------------------------------------
1 | # 🖥️ Trivial Pixels Demo ⚙️
2 |
3 | ## Overview
4 |
5 | The `trivial_pixels_demo` is a very simple demonstration of the Winit/Pixels Output Driver for PixelLoop, which allows rendering to a gpu backed window surface.
6 |
7 | ## Build Instructions
8 |
9 | To build the Trivial Winit Demo run `cargo build --release`.
10 |
11 | ## Usage
12 |
13 | Once built, run the `targets/release/trivial_pixels_demo` binary to start the demo.
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/trivial_cli_demo/README.md:
--------------------------------------------------------------------------------
1 | # 💻 Trivial CLI Demo ⚙️
2 |
3 | ## Overview
4 |
5 | The `trivial_cli_demo` is a very simple demonstration of the Crossterm Output Driver for PixelLoop, which allows rendering to a shell console using unicode characters and ansi colors.
6 |
7 | ## Build Instructions
8 |
9 | To build the Trivial CLI Demo run `cargo build --release`.
10 |
11 | ## Usage
12 |
13 | Once built, run the `targets/release/trivial_cli_demo` binary to start the demo. Use the `arrow keys` to move the violet rectangle around. Press `space` to randomize colors and `q` to exit.
14 |
15 |
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG - pixel_loop
2 |
3 | # 0.3.0 - 10.11.2024
4 |
5 | - Feature: Implement way to exit the loop cleanly
6 |
7 | - Change: Definition of Update and Render functions to support loop exit handling
8 |
9 | - Change: Refactor examples to use new NextLoopState
10 |
11 | - Fix: Cleanup terminal state when CrosstermCanvas application is exited.
12 |
13 | # 0.2.0 - 10.11.2024
14 |
15 | - Fix: Enable `InMemoryCanvas` regardless of `image-load` feature flag
16 |
17 | - Change: Restructure feature flag naming: `image-load` -> `stb-image`
18 |
19 | - Change: Moved winit intialization into the `PixelsCanvas`
20 |
21 | - Feature: Glued InputStates to corresponding RenderableCanvas implementation via trait internal type.
22 |
23 | - Change: Refactor Resizing handling and introduce `did_resize` state function.
24 |
25 | - Change: Updated examples to represent changed API
26 |
27 |
28 | # 0.1.1 - 06.11.2024
29 |
30 | - Fix of flipped green and blue color components in `Color` factories.
31 |
32 | # 0.1.0 - 04.11.2024
33 |
34 | - First release as a standalone library on cargo.io
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © 2024 Jakob Westhoff
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the “Software”), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pixel_loop"
3 | authors = ["Jakob Westhoff "]
4 | version = "0.3.0"
5 | edition = "2021"
6 | description = "A pixel based drawing engine based on the idea of a self stabilizing update loop."
7 | license = "MIT"
8 | repository = "https://github.com/jakobwesthoff/pixel_loop"
9 | documentation = "https://docs.rs/pixel_loop"
10 |
11 | [lib]
12 | name = "pixel_loop"
13 | path = "src/pixel_loop/lib.rs"
14 |
15 | [dependencies]
16 | anyhow = "1.0.92"
17 | crossterm = { version = "0.28.1", optional = true }
18 | pixels = { version = "0.13.0", optional = true }
19 | rand = "0.8.5"
20 | rand_xoshiro = "0.6.0"
21 | stb_image = { version = "0.3.0", optional = true }
22 | winit = { version = "0.28.0", optional = true }
23 | winit_input_helper = { version = "0.14.0", optional = true }
24 |
25 | [profile.dev]
26 | opt-level = 1
27 | [profile.dev.package."*"]
28 | opt-level = 3
29 |
30 | [features]
31 | # For now everything is enabled by default. We might change this in future
32 | # versions, once the API becomes more stable.
33 | default = ["crossterm", "pixels", "stb-image"]
34 |
35 | # Enable console rendering capabilities via the "CrosstermCanvas"
36 | crossterm = ["dep:crossterm"]
37 |
38 | # Enable Window handling and rendering capabilities, via winit and pixels
39 | pixels = ["dep:winit", "dep:winit_input_helper", "dep:pixels"]
40 |
41 | # Allow loading of images via stb-image and InMemoryCanvas
42 | stb-image = ["dep:stb_image"]
43 |
--------------------------------------------------------------------------------
/examples/trivial_pixels_demo/src/main.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use pixel_loop::canvas::{Canvas, PixelsCanvas, RenderableCanvas};
3 | use pixel_loop::color::Color;
4 | use pixel_loop::input::{KeyboardKey, KeyboardState, PixelsInputState};
5 | use pixel_loop::rand::Rng;
6 | use pixel_loop::NextLoopState;
7 |
8 | struct FlyingBox {
9 | x: i64,
10 | y: i64,
11 | width: u32,
12 | height: u32,
13 | speed_x: i64,
14 | speed_y: i64,
15 | color: Color,
16 | }
17 |
18 | struct State {
19 | flying_box: FlyingBox,
20 | }
21 |
22 | impl State {
23 | fn new() -> Self {
24 | Self {
25 | flying_box: FlyingBox {
26 | x: 0,
27 | y: 0,
28 | width: 64,
29 | height: 64,
30 | speed_x: 2,
31 | speed_y: 2,
32 | color: Color::from_rgb(156, 80, 182),
33 | },
34 | }
35 | }
36 | }
37 |
38 | fn main() -> Result<()> {
39 | let width = 640;
40 | let height = 480;
41 |
42 | let canvas = PixelsCanvas::new(width, height, Some(2), "pixel_loop", true)?;
43 | let input = PixelsInputState::new();
44 | let state = State::new();
45 |
46 | pixel_loop::run(
47 | 120,
48 | state,
49 | input,
50 | canvas,
51 | |e, s, i, canvas| {
52 | if i.is_key_pressed(KeyboardKey::Space) {
53 | // Randomise color on press of space
54 | s.flying_box.color =
55 | Color::from_rgb(e.rand.gen::(), e.rand.gen::(), e.rand.gen::());
56 | }
57 |
58 | s.flying_box.x += s.flying_box.speed_x;
59 | s.flying_box.y += s.flying_box.speed_y;
60 | if s.flying_box.x + s.flying_box.width as i64 >= canvas.width() as i64
61 | || s.flying_box.x <= 0
62 | {
63 | s.flying_box.speed_x *= -1;
64 | s.flying_box.x += s.flying_box.speed_x;
65 | }
66 | if s.flying_box.y + s.flying_box.height as i64 >= canvas.height() as i64
67 | || s.flying_box.y <= 0
68 | {
69 | s.flying_box.speed_y *= -1;
70 | s.flying_box.y += s.flying_box.speed_y;
71 | }
72 |
73 | Ok(NextLoopState::Continue)
74 | },
75 | |e, s, i, canvas, dt| {
76 | let width = canvas.width();
77 | let height = canvas.height();
78 |
79 | // RENDER BEGIN
80 | canvas.clear_screen(&Color::from_rgb(0, 0, 0));
81 | canvas.filled_rect(
82 | s.flying_box.x,
83 | s.flying_box.y,
84 | s.flying_box.width,
85 | s.flying_box.height,
86 | &s.flying_box.color,
87 | );
88 | // RENDER END
89 |
90 | canvas.render()?;
91 |
92 | Ok(NextLoopState::Continue)
93 | },
94 | );
95 | }
96 |
--------------------------------------------------------------------------------
/src/pixel_loop/tao.rs:
--------------------------------------------------------------------------------
1 | use super::{EngineEnvironment, PixelLoop, PixelsCanvas, RenderFn, UpdateFn};
2 | use anyhow::{Context, Result};
3 | use pixels::{Pixels, SurfaceTexture};
4 | use tao::dpi::LogicalSize;
5 | use tao::event::{Event, WindowEvent};
6 | use tao::event_loop::{ControlFlow, EventLoop};
7 | use tao::window::{Window, WindowBuilder};
8 |
9 | type TaoEventFn = fn(
10 | &mut EngineEnvironment,
11 | &mut State,
12 | &mut CanvasImpl,
13 | &Window,
14 | event: &Event<()>,
15 | ) -> Result<()>;
16 |
17 | pub struct TaoContext {
18 | event_loop: EventLoop<()>,
19 | window: Window,
20 | }
21 |
22 | impl TaoContext {
23 | pub fn as_window(&self) -> &Window {
24 | &self.window
25 | }
26 | }
27 |
28 | pub fn init_window(
29 | title: &str,
30 | min_width: u32,
31 | min_height: u32,
32 | resizable: bool,
33 | ) -> Result {
34 | let event_loop = EventLoop::new();
35 | let window = {
36 | let size = LogicalSize::new(min_width, min_height);
37 | WindowBuilder::new()
38 | .with_title(title)
39 | .with_inner_size(size)
40 | .with_min_inner_size(size)
41 | .with_resizable(resizable)
42 | .build(&event_loop)?
43 | };
44 |
45 | Ok(TaoContext { event_loop, window })
46 | }
47 |
48 | pub fn init_pixels(context: &TaoContext, width: u32, height: u32) -> Result {
49 | let physical_dimensions = context.as_window().inner_size();
50 | let surface_texture = SurfaceTexture::new(
51 | physical_dimensions.width,
52 | physical_dimensions.height,
53 | context.as_window(),
54 | );
55 | let pixels = Pixels::new(width, height, surface_texture).context("create pixels surface")?;
56 | Ok(PixelsCanvas::new(pixels))
57 | }
58 |
59 | pub fn run(
60 | state: State,
61 | context: TaoContext,
62 | canvas: PixelsCanvas,
63 | update: UpdateFn,
64 | render: RenderFn,
65 | handle_event: TaoEventFn,
66 | ) -> ! {
67 | let mut pixel_loop = PixelLoop::new(120, state, canvas, update, render);
68 | context.event_loop.run(move |event, _, control_flow| {
69 | handle_event(
70 | &mut pixel_loop.engine_state,
71 | &mut pixel_loop.state,
72 | &mut pixel_loop.canvas,
73 | &context.window,
74 | &event,
75 | )
76 | .context("handle user events")
77 | .unwrap();
78 | match event {
79 | Event::MainEventsCleared => {
80 | pixel_loop
81 | .next_loop()
82 | .context("run next pixel loop")
83 | .unwrap();
84 | }
85 | Event::WindowEvent {
86 | event: win_event, ..
87 | } => match win_event {
88 | WindowEvent::CloseRequested => {
89 | *control_flow = ControlFlow::Exit;
90 | }
91 | _ => {}
92 | },
93 |
94 | _ => {}
95 | }
96 | });
97 | }
98 |
--------------------------------------------------------------------------------
/src/pixel_loop/canvas/in_memory.rs:
--------------------------------------------------------------------------------
1 | //! In-memory canvas implementation with image loading capabilities.
2 | //!
3 | //! This module provides a basic canvas implementation that stores pixel data in memory.
4 | //!
5 | //! It can be used to load image data from disk if the `stb-image` feature is
6 | //! enabled.
7 |
8 | use super::Canvas;
9 | use crate::color::Color;
10 | use std::ops::Range;
11 |
12 | #[cfg(feature = "stb-image")]
13 | use anyhow::anyhow;
14 | #[cfg(feature = "stb-image")]
15 | use anyhow::Result;
16 |
17 | /// A canvas implementation that stores pixel data in memory.
18 | ///
19 | /// This canvas provides basic pixel manipulation operations and can be used
20 | /// to load and manipulate images in memory.
21 | pub struct InMemoryCanvas {
22 | /// The pixel buffer storing all colors
23 | buffer: Vec,
24 | /// Width of the canvas in pixels
25 | width: u32,
26 | /// Height of the canvas in pixels
27 | height: u32,
28 | }
29 |
30 | impl InMemoryCanvas {
31 | /// Creates a new blank canvas with the specified dimensions and background color.
32 | ///
33 | /// # Arguments
34 | /// * `width` - The width of the canvas in pixels
35 | /// * `height` - The height of the canvas in pixels
36 | /// * `color` - The initial color to fill the canvas with
37 | ///
38 | /// # Examples
39 | /// ```
40 | /// use pixel_loop::{canvas::InMemoryCanvas, color::Color};
41 | ///
42 | /// let canvas = InMemoryCanvas::new(640, 480, &Color::from_rgb(0, 0, 0));
43 | /// ```
44 | pub fn new(width: u32, height: u32, color: &Color) -> Self {
45 | Self {
46 | buffer: vec![*color; (width * height) as usize],
47 | width,
48 | height,
49 | }
50 | }
51 |
52 | /// Creates a new canvas by loading an image from a memory buffer.
53 | ///
54 | /// This method supports all image formats (non HDR), that can be read by the
55 | /// [stb_image](https://github.com/nothings/stb/blob/master/stb_image.h)
56 | /// library.
57 | ///
58 | /// Only available if the `stb-image` feature is enabled.
59 | ///
60 | /// # Arguments
61 | /// * `bytes` - Raw image bytes to load
62 | ///
63 | /// # Returns
64 | /// * `Ok(InMemoryCanvas)` - Successfully loaded canvas
65 | /// * `Err` - If the image couldn't be loaded or has an unsupported format
66 | ///
67 | /// # Errors
68 | /// Returns an error if:
69 | /// * The image data is invalid or corrupted
70 | /// * The image is HDR (32-bit float)
71 | /// * The image depth is not 3 (RGB)
72 | ///
73 | /// # Examples
74 | /// ```
75 | /// use pixel_loop::canvas::InMemoryCanvas;
76 | ///
77 | /// let image_bytes = std::fs::read("example.jpg").unwrap();
78 | /// let canvas = InMemoryCanvas::from_in_memory_image(&image_bytes)?;
79 | /// ```
80 | #[cfg(feature = "stb-image")]
81 | pub fn from_in_memory_image(bytes: &[u8]) -> Result {
82 | use stb_image::image;
83 | use stb_image::image::LoadResult::*;
84 | match image::load_from_memory(bytes) {
85 | Error(msg) => Err(anyhow!("Could not load image from memory: {msg}")),
86 | ImageF32(_) => Err(anyhow!("Could not load hdr image from memory")),
87 | ImageU8(image) => {
88 | if image.depth != 3 {
89 | return Err(anyhow!(
90 | "Could not load image with depth != 3. It has {depth}",
91 | depth = image.depth
92 | ));
93 | }
94 |
95 | let mut buffer: Vec = Vec::with_capacity(image.width * image.height);
96 | for i in (0..image.width * image.height * image.depth).step_by(image.depth) {
97 | buffer.push(Color::from_rgb(
98 | image.data[i],
99 | image.data[i + 1],
100 | image.data[i + 2],
101 | ))
102 | }
103 |
104 | Ok(Self {
105 | width: image.width as u32,
106 | height: image.height as u32,
107 | buffer,
108 | })
109 | }
110 | }
111 | }
112 | }
113 |
114 | impl Canvas for InMemoryCanvas {
115 | fn width(&self) -> u32 {
116 | self.width
117 | }
118 |
119 | fn height(&self) -> u32 {
120 | self.height
121 | }
122 |
123 | fn set_range(&mut self, range: Range, color: &[Color]) {
124 | self.buffer[range].copy_from_slice(color);
125 | }
126 |
127 | fn get_range(&self, range: Range) -> &[Color] {
128 | &self.buffer[range]
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🎨 Pixel Loop 🔁
2 |
3 | [](https://crates.io/crates/pixel_loop)
4 | [](https://docs.rs/pixel_loop)
5 |
6 | ## Warning: **WORK IN PROGRESS**
7 |
8 | This crate/library is still heavily being worked on. The API is not considered to be stable at this point in time. If you want to follow the development check out the following youtube channel [MrJakob](https://youtube.com/c/mrjakob).
9 |
10 | ## About
11 |
12 | A Rust game loop implementation providing a solid foundation for building games and interactive applications. Inspired by the concepts from [Fix Your Timestep](https://gafferongames.com/post/fix_your_timestep/), it offers fixed timestep updates with variable rendering, supporting both windowed and terminal-based applications.
13 |
14 | ## Motivation
15 |
16 | The idea behind Pixel Loop resonated with me as I have often faced challenges with timing aspects while working on animations from scratch. This project serves as a practical exploration of fixed time game/update loops and lays the groundwork for future experiments and projects.
17 |
18 | ## Installation
19 |
20 | Add Pixel Loop to your `Cargo.toml`:
21 |
22 | ```toml
23 | [dependencies]
24 | pixel_loop = "*"
25 | ```
26 |
27 | ### Feature Flags
28 |
29 | - `winit` - Enable window-based rendering
30 | - `crossterm` - Enable terminal-based rendering
31 | - `stb-image` - Enable image loading support for InMemoryCanvas via stb_image
32 |
33 | By default all flags are currently enabled. If you only need a specific one, you may only use enable the backend/feature you specifically need, to cut down on compilation time and filesize.
34 |
35 | ## Examples
36 |
37 | ### Terminal Application
38 |
39 | Create a simple moving box in your terminal:
40 |
41 | ```rust
42 | use pixel_loop::{run, canvas::CrosstermCanvas, input::CrosstermInputState};
43 | use pixel_loop::color::Color;
44 | use pixel_loop::input::KeyboardKey;
45 | use anyhow::Result;
46 |
47 | struct GameState {
48 | box_pos: (i64, i64),
49 | }
50 |
51 | fn main() -> Result<()> {
52 | let mut canvas = CrosstermCanvas::new(); // Terminal size
53 |
54 | let state = GameState { box_pos: (0, 0) };
55 | let input = CrosstermInputState::new();
56 |
57 | run(
58 | 60, // Updates per second
59 | state,
60 | input,
61 | canvas,
62 | // Update function - fixed timestep
63 | |_env, state, input, _canvas| {
64 | if input.is_key_down(KeyboardKey::Right) {
65 | state.box_pos.0 += 1;
66 | }
67 | if input.is_key_pressed(KeyboardKey::Q) {
68 | std::process::exit(0);
69 | }
70 | Ok(())
71 | },
72 | // Render function - variable timestep
73 | |_env, state, _input, canvas, _dt| {
74 | canvas.clear_screen(&Color::from_rgb(0, 0, 0));
75 | canvas.filled_rect(
76 | state.box_pos.0,
77 | state.box_pos.1,
78 | 5,
79 | 5,
80 | &Color::from_rgb(255, 0, 0),
81 | );
82 | canvas.render()?;
83 | Ok(())
84 | },
85 | )
86 | }
87 | ```
88 |
89 | ## Architecture
90 |
91 | ### Game Loop
92 |
93 | Pixel Loop implements a fixed timestep game loop that:
94 |
95 | - Updates game logic at a constant rate (configurable FPS)
96 | - Renders as fast as possible while maintaining update consistency
97 | - Handles timing and frame limiting automatically
98 |
99 | ### Canvas System
100 |
101 | The library provides (currently) three canvas implementations:
102 |
103 | - `PixelsCanvas`: Hardware-accelerated window rendering
104 | - `CrosstermCanvas`: Terminal-based rendering using Unicode characters
105 | - `InMemoryCanvas`: In-memory buffer for image manipulation
106 |
107 | Each canvas (currently) supports:
108 |
109 | - Basic shape rendering (rectangles)
110 | - Color management (RGB and HSL)
111 | - Efficient blitting operations
112 | - Custom viewport management
113 |
114 | ### Input System
115 |
116 | Input handling is abstracted through traits:
117 |
118 | - `KeyboardState` for basic keyboard input
119 | - `InputState` for game loop integration
120 | - Support for key press, release, and hold states
121 | - Cross-platform compatibility
122 |
123 | **Note: Mouse integration for window rendering can currently be archieved, but an abstraction has not yet been implemented**
124 |
125 | ## Contributing
126 |
127 | Contributions are welcome! Please feel free to submit issues and pull requests.
128 |
129 | ## License
130 |
131 | This project is licensed under the MIT License - see the LICENSE file for details.
132 |
133 | ## Acknowledgments
134 |
135 | - Inspired by [Fix Your Timestep](https://gafferongames.com/post/fix_your_timestep/)
136 | - Built with [pixels](https://github.com/parasyte/pixels) and [crossterm](https://github.com/crossterm-rs/crossterm)
137 |
138 | ## Subprojects
139 |
140 | This repository housed a couple of different experiment implementations based on
141 | `pixel_loop`. Those have mostly have been moved to their own repositories as
142 | the library is now published on crates.io.
143 |
144 | You can find the old subprojects here:
145 |
146 | * [pixel_sand](https://github.com/jakobwesthoff/pixel_sand) - A sand movement simulator.
147 | * [tetrotime](https://github.com/jakobwesthoff/tetrotime) - A Tetromino based clock, stopwatch and timer.
148 | * [trivial_cli_demo](examples/trivial_cli_demo/README.md) - A trivial demo showing the CLI/Shell Unicode and ANSI based output driver.
149 | * [shell_smash](https://github.com/jakobwesthofF/shell_smash) - A simple breakout clone running in your Terminal.
150 | * [fireworks](https://github.com/jakobwesthoff/pixel_fireworks) - Fireworks particle simulation in your Terminal
151 |
--------------------------------------------------------------------------------
/examples/trivial_cli_demo/src/main.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use pixel_loop::canvas::{Canvas, CrosstermCanvas, RenderableCanvas};
3 | use pixel_loop::input::{CrosstermInputState, KeyboardKey, KeyboardState};
4 | use pixel_loop::rand::Rng;
5 | use pixel_loop::{color::*, NextLoopState};
6 |
7 | struct Box {
8 | box_position: (i64, i64),
9 | box_direction: (i64, i64),
10 | box_size: (u32, u32),
11 | color: Color,
12 | shadow_color: Color,
13 | }
14 |
15 | struct State {
16 | my_box: Box,
17 | boxes: Vec,
18 | }
19 |
20 | impl State {
21 | fn new() -> Self {
22 | Self {
23 | my_box: Box {
24 | box_position: (0, 0),
25 | box_direction: (1, 1),
26 | box_size: (5, 5),
27 | color: Color::from_rgb(156, 80, 182),
28 | shadow_color: Color::from_rgb(104, 71, 141),
29 | },
30 | boxes: vec![
31 | Box {
32 | box_position: (0, 0),
33 | box_direction: (1, 1),
34 | box_size: (20, 10),
35 | color: Color::from_rgb(255, 255, 128),
36 | shadow_color: Color::from_rgb(128, 128, 64),
37 | },
38 | Box {
39 | box_position: (0, 4),
40 | box_direction: (2, 1),
41 | box_size: (5, 5),
42 | color: Color::from_rgb(128, 255, 128),
43 | shadow_color: Color::from_rgb(64, 128, 64),
44 | },
45 | Box {
46 | box_position: (0, 23),
47 | box_direction: (1, 2),
48 | box_size: (20, 20),
49 | color: Color::from_rgb(255, 128, 64),
50 | shadow_color: Color::from_rgb(128, 64, 32),
51 | },
52 | Box {
53 | box_position: (0, 10),
54 | box_direction: (2, 2),
55 | box_size: (10, 10),
56 | color: Color::from_rgb(255, 0, 128),
57 | shadow_color: Color::from_rgb(128, 0, 64),
58 | },
59 | ],
60 | }
61 | }
62 | }
63 |
64 | fn main() -> Result<()> {
65 | let canvas = CrosstermCanvas::new();
66 |
67 | let state = State::new();
68 | let input = CrosstermInputState::new();
69 |
70 | eprintln!("Render size: {}x{}", canvas.width(), canvas.height());
71 |
72 | pixel_loop::run(
73 | 60,
74 | state,
75 | input,
76 | canvas,
77 | |e, s, input, canvas| {
78 | let width = canvas.width();
79 | let height = canvas.height();
80 |
81 | if input.is_key_pressed(KeyboardKey::Q) {
82 | return Ok(NextLoopState::Exit(0));
83 | }
84 |
85 | if input.is_key_pressed(KeyboardKey::Space) {
86 | for b in s.boxes.iter_mut() {
87 | b.color = Color::from_rgb(e.rand.gen(), e.rand.gen(), e.rand.gen());
88 | let mut shadow_color = b.color.as_hsl();
89 | shadow_color.s = (shadow_color.s - 20.0).clamp(0.0, 100.0);
90 | shadow_color.l = (shadow_color.l - 20.0).clamp(0.0, 100.0);
91 | b.shadow_color = Color::from(shadow_color);
92 | }
93 | }
94 |
95 | if input.is_key_down(KeyboardKey::Up) {
96 | s.my_box.box_position.1 -= 1;
97 | }
98 | if input.is_key_down(KeyboardKey::Down) {
99 | s.my_box.box_position.1 += 1;
100 | }
101 | if input.is_key_down(KeyboardKey::Left) {
102 | s.my_box.box_position.0 -= 1;
103 | }
104 | if input.is_key_down(KeyboardKey::Right) {
105 | s.my_box.box_position.0 += 1;
106 | }
107 |
108 | for b in s.boxes.iter_mut() {
109 | let (mut px, mut py) = b.box_position;
110 | let (mut dx, mut dy) = b.box_direction;
111 | let (sx, sy) = b.box_size;
112 | px += dx;
113 | py += dy;
114 |
115 | if px < 0 || px + sx as i64 >= width as i64 {
116 | dx *= -1;
117 | px += dx;
118 | }
119 | if py < 0 || py + sy as i64 >= height as i64 {
120 | dy *= -1;
121 | py += dy;
122 | }
123 |
124 | b.box_position = (px, py);
125 | b.box_direction = (dx, dy);
126 | }
127 |
128 | Ok(NextLoopState::Continue)
129 | },
130 | |e, s, i, canvas, dt| {
131 | // RENDER BEGIN
132 | canvas.clear_screen(&Color::from_rgb(0, 0, 0));
133 |
134 | for b in s.boxes.iter() {
135 | canvas.filled_rect(
136 | b.box_position.0 + 2,
137 | b.box_position.1 + 2,
138 | b.box_size.0,
139 | b.box_size.1,
140 | &b.shadow_color,
141 | );
142 | canvas.filled_rect(
143 | b.box_position.0,
144 | b.box_position.1,
145 | b.box_size.0,
146 | b.box_size.1,
147 | &b.color,
148 | );
149 | }
150 | canvas.filled_rect(
151 | s.my_box.box_position.0 + 1,
152 | s.my_box.box_position.1 + 1,
153 | s.my_box.box_size.0,
154 | s.my_box.box_size.1,
155 | &s.my_box.shadow_color,
156 | );
157 | canvas.filled_rect(
158 | s.my_box.box_position.0,
159 | s.my_box.box_position.1,
160 | s.my_box.box_size.0,
161 | s.my_box.box_size.1,
162 | &s.my_box.color,
163 | );
164 |
165 | // RENDER END
166 |
167 | canvas.render()?;
168 |
169 | Ok(NextLoopState::Continue)
170 | },
171 | );
172 | }
173 |
--------------------------------------------------------------------------------
/src/pixel_loop/input/mod.rs:
--------------------------------------------------------------------------------
1 | //! Input handling and keyboard state management.
2 | //!
3 | //! This module provides traits and types for handling keyboard input across different
4 | //! platforms. It defines a comprehensive set of keyboard keys and traits for tracking
5 | //! keyboard state and input processing.
6 |
7 | #[cfg(feature = "crossterm")]
8 | pub mod crossterm;
9 | #[cfg(feature = "crossterm")]
10 | pub use crossterm::CrosstermInputState;
11 |
12 | #[cfg(feature = "pixels")]
13 | pub mod pixels;
14 | #[cfg(feature = "pixels")]
15 | pub use pixels::PixelsInputState;
16 |
17 | use anyhow::Result;
18 |
19 | use crate::NextLoopState;
20 |
21 | /// Represents all possible keyboard keys that can be handled.
22 | ///
23 | /// This enum provides a comprehensive list of keyboard keys including:
24 | /// - Alphanumeric keys (A-Z, 0-9)
25 | /// - Special characters (comma, period, etc.)
26 | /// - Function keys (F1-F12)
27 | /// - Navigation keys (arrows, home, end, etc.)
28 | /// - Modifier keys (shift, control, alt, etc.)
29 | /// - Keypad keys
30 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31 | pub enum KeyboardKey {
32 | // Alphanumeric keys
33 | /// Key: '
34 | Apostrophe,
35 | /// Key: ,
36 | Comma,
37 | /// Key: -
38 | Minus,
39 | /// Key: .
40 | Period,
41 | /// Key: /
42 | Slash,
43 | /// Key: 0
44 | Zero,
45 | /// Key: 1
46 | One,
47 | /// Key: 2
48 | Two,
49 | /// Key: 3
50 | Three,
51 | /// Key: 4
52 | Four,
53 | /// Key: 5
54 | Five,
55 | /// Key: 6
56 | Six,
57 | /// Key: 7
58 | Seven,
59 | /// Key: 8
60 | Eight,
61 | /// Key: 9
62 | Nine,
63 | /// Key: ;
64 | Semicolon,
65 | /// Key: =
66 | Equal,
67 | /// Key: A | a
68 | A,
69 | /// Key: B | b
70 | B,
71 | /// Key: C | c
72 | C,
73 | /// Key: D | d
74 | D,
75 | /// Key: E | e
76 | E,
77 | /// Key: F | f
78 | F,
79 | /// Key: G | g
80 | G,
81 | /// Key: H | h
82 | H,
83 | /// Key: I | i
84 | I,
85 | /// Key: J | j
86 | J,
87 | /// Key: K | k
88 | K,
89 | /// Key: L | l
90 | L,
91 | /// Key: M | m
92 | M,
93 | /// Key: N | n
94 | N,
95 | /// Key: O | o
96 | O,
97 | /// Key: P | p
98 | P,
99 | /// Key: Q | q
100 | Q,
101 | /// Key: R | r
102 | R,
103 | /// Key: S | s
104 | S,
105 | /// Key: T | t
106 | T,
107 | /// Key: U | u
108 | U,
109 | /// Key: V | v
110 | V,
111 | /// Key: W | w
112 | W,
113 | /// Key: X | x
114 | X,
115 | /// Key: Y | y
116 | Y,
117 | /// Key: Z | z
118 | Z,
119 | /// Key: [
120 | LeftBracket,
121 | /// Key: '\'
122 | Backslash,
123 | /// Key: ]
124 | RightBracket,
125 | /// Key: `
126 | Grave,
127 |
128 | // Function keys
129 | /// Key: Space
130 | Space,
131 | /// Key: Esc
132 | Escape,
133 | /// Key: Enter
134 | Enter,
135 | /// Key: Tab
136 | Tab,
137 | /// Key: Backspace
138 | Backspace,
139 | /// Key: Ins
140 | Insert,
141 | /// Key: Del
142 | Delete,
143 | /// Key: Cursor right
144 | Right,
145 | /// Key: Cursor left
146 | Left,
147 | /// Key: Cursor down
148 | Down,
149 | /// Key: Cursor up
150 | Up,
151 | /// Key: Page up
152 | PageUp,
153 | /// Key: Page down
154 | PageDown,
155 | /// Key: Home
156 | Home,
157 | /// Key: End
158 | End,
159 | /// Key: Caps lock
160 | CapsLock,
161 | /// Key: Scroll down
162 | ScrollLock,
163 | /// Key: Num lock
164 | NumLock,
165 | /// Key: Print screen
166 | PrintScreen,
167 | /// Key: Pause
168 | Pause,
169 | /// Key: F1
170 | F1,
171 | /// Key: F2
172 | F2,
173 | /// Key: F3
174 | F3,
175 | /// Key: F4
176 | F4,
177 | /// Key: F5
178 | F5,
179 | /// Key: F6
180 | F6,
181 | /// Key: F7
182 | F7,
183 | /// Key: F8
184 | F8,
185 | /// Key: F9
186 | F9,
187 | /// Key: F10
188 | F10,
189 | /// Key: F11
190 | F11,
191 | /// Key: F12
192 | F12,
193 | /// Key: Shift left
194 | LeftShift,
195 | /// Key: Control left
196 | LeftControl,
197 | /// Key: Alt left
198 | LeftAlt,
199 | /// Key: Super left
200 | LeftSuper,
201 | /// Key: Shift right
202 | RightShift,
203 | /// Key: Control right
204 | RightControl,
205 | /// Key: Alt right
206 | RightAlt,
207 | /// Key: Super right
208 | RightSuper,
209 | /// Key: KB menu
210 | KbMenu,
211 |
212 | // Keypad keys
213 | /// Key: Keypad 0
214 | Kp0,
215 | /// Key: Keypad 1
216 | Kp1,
217 | /// Key: Keypad 2
218 | Kp2,
219 | /// Key: Keypad 3
220 | Kp3,
221 | /// Key: Keypad 4
222 | Kp4,
223 | /// Key: Keypad 5
224 | Kp5,
225 | /// Key: Keypad 6
226 | Kp6,
227 | /// Key: Keypad 7
228 | Kp7,
229 | /// Key: Keypad 8
230 | Kp8,
231 | /// Key: Keypad 9
232 | Kp9,
233 | /// Key: Keypad .
234 | KpDecimal,
235 | /// Key: Keypad /
236 | KpDivide,
237 | /// Key: Keypad *
238 | KpMultiply,
239 | /// Key: Keypad -
240 | KpSubtract,
241 | /// Key: Keypad +
242 | KpAdd,
243 | /// Key: Keypad Enter
244 | KpEnter,
245 | /// Key: Keypad =
246 | KpEqual,
247 | }
248 |
249 | /// Trait for tracking keyboard state.
250 | ///
251 | /// This trait provides methods for checking the current state of keyboard keys,
252 | /// including whether they were just pressed, are being held down, were just
253 | /// released, or are currently up.
254 | pub trait KeyboardState {
255 | /// Checks if a key was pressed this frame.
256 | ///
257 | /// # Arguments
258 | /// * `key` - The key to check
259 | fn is_key_pressed(&self, key: KeyboardKey) -> bool;
260 |
261 | /// Checks if a key is currently being held down.
262 | ///
263 | /// # Arguments
264 | /// * `key` - The key to check
265 | fn is_key_down(&self, key: KeyboardKey) -> bool;
266 |
267 | /// Checks if a key was released this frame.
268 | ///
269 | /// # Arguments
270 | /// * `key` - The key to check
271 | fn is_key_released(&self, key: KeyboardKey) -> bool;
272 |
273 | /// Checks if a key is currently up (not being pressed).
274 | ///
275 | /// # Arguments
276 | /// * `key` - The key to check
277 | fn is_key_up(&self, key: KeyboardKey) -> bool;
278 | }
279 |
280 | /// Trait for managing input state in a game loop.
281 | ///
282 | /// This trait extends `KeyboardState` and provides methods for managing input
283 | /// state throughout the lifecycle of a game loop.
284 | ///
285 | /// Its methods provide a way for different platform implementations to hook
286 | /// into the game loop cycle to handle input event processing.
287 | pub trait InputState: KeyboardState {
288 | /// Initializes the input state before starting a loop.
289 | ///
290 | /// This method is called once before entering the main loop.
291 | fn begin(&mut self) -> Result<()>;
292 |
293 | /// Updates the input state for the next frame.
294 | ///
295 | /// This method is called at the beginning of each loop iteration, before the
296 | /// update function is invoked.
297 | fn next_loop(&mut self) -> Result;
298 |
299 | /// Finalizes the input state after the loop ends.
300 | ///
301 | /// This method is called once after exiting the main loop.
302 | fn finish(&mut self) -> Result<()>;
303 | }
304 |
--------------------------------------------------------------------------------
/src/pixel_loop/color.rs:
--------------------------------------------------------------------------------
1 | //! Color types and conversion utilities.
2 | //!
3 | //! This module provides RGB and HSL color representations along with conversion
4 | //! functions between different color spaces. It also includes utilities for
5 | //! handling color data as byte slices.
6 |
7 | /// An RGBA color representation.
8 | ///
9 | /// Each color component (red, green, blue, alpha) is stored as an 8-bit
10 | /// unsigned integer, giving a range of 0-255 for each channel.
11 | #[repr(C)]
12 | #[derive(Clone, Copy, PartialEq, Debug)]
13 | pub struct Color {
14 | /// Red component [0-255]
15 | pub r: u8,
16 | /// Green component [0-255]
17 | pub g: u8,
18 | /// Blue component [0-255]
19 | pub b: u8,
20 | /// Alpha component [0-255]
21 | pub a: u8,
22 | }
23 |
24 | /// Trait for converting color data to raw bytes.
25 | ///
26 | /// This trait enables efficient conversion of color data to byte slices
27 | /// without copying the underlying data.
28 | pub trait ColorAsByteSlice {
29 | /// Converts the color data to a raw byte slice.
30 | fn as_byte_slice(&self) -> &[u8];
31 | }
32 |
33 | impl ColorAsByteSlice for [Color] {
34 | fn as_byte_slice(&self) -> &[u8] {
35 | let byte_slice = unsafe {
36 | std::slice::from_raw_parts(
37 | self.as_ptr() as *const u8,
38 | std::mem::size_of_val(self),
39 | )
40 | };
41 | byte_slice
42 | }
43 | }
44 |
45 | impl Color {
46 | /// Creates a new Color from a slice of bytes.
47 | ///
48 | /// # Arguments
49 | /// * `bytes` - Raw byte slice containing RGBA color data
50 | ///
51 | /// # Panics
52 | /// * If the byte slice length is not a multiple of 4
53 | /// * If the byte slice is not properly aligned for Color struct
54 | ///
55 | /// # Examples
56 | /// ```
57 | /// use my_crate::Color;
58 | ///
59 | /// let bytes = [255, 0, 0, 255, 0, 255, 0, 255];
60 | /// let colors = Color::from_bytes(&bytes);
61 | /// assert_eq!(colors.len(), 2);
62 | /// ```
63 | pub fn from_bytes(bytes: &[u8]) -> &[Self] {
64 | if bytes.len() % std::mem::size_of::() != 0 {
65 | panic!("Color slices can only be initialized with a multiple of 4 byte slices");
66 | }
67 |
68 | let color_slice = unsafe {
69 | if bytes.as_ptr() as usize % std::mem::align_of::() != 0 {
70 | panic!(
71 | "alignment of color byte slice must be fitting for alignment of Color struct"
72 | )
73 | }
74 |
75 | std::slice::from_raw_parts(
76 | bytes.as_ptr() as *const Color,
77 | bytes.len() / std::mem::size_of::(),
78 | )
79 | };
80 |
81 | color_slice
82 | }
83 |
84 | /// Creates a new Color from RGBA components.
85 | ///
86 | /// # Arguments
87 | /// * `r` - Red component [0-255]
88 | /// * `g` - Green component [0-255]
89 | /// * `b` - Blue component [0-255]
90 | /// * `a` - Alpha component [0-255]
91 | ///
92 | /// # Examples
93 | /// ```
94 | /// use pixel_loop::color::Color;
95 | ///
96 | /// let color = Color::from_rgba(255, 0, 0, 255); // Opaque red
97 | /// ```
98 | pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
99 | Self { r, g, b, a }
100 | }
101 |
102 | /// Creates a new opaque Color from RGB components.
103 | ///
104 | /// # Arguments
105 | /// * `r` - Red component [0-255]
106 | /// * `g` - Green component [0-255]
107 | /// * `b` - Blue component [0-255]
108 | ///
109 | /// # Examples
110 | /// ```
111 | /// use pixel_loop::color::Color;
112 | ///
113 | /// let color = Color::from_rgb(255, 0, 0); // Opaque red
114 | /// ```
115 | pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
116 | Self { r, g, b, a: 255 }
117 | }
118 |
119 | /// Converts the color to a raw byte slice.
120 | ///
121 | /// # Returns
122 | /// A slice containing the raw RGBA bytes of the color
123 | pub fn as_bytes(&self) -> &[u8] {
124 | let color_slice = std::slice::from_ref(self);
125 | let byte_slice = unsafe {
126 | std::slice::from_raw_parts(
127 | color_slice.as_ptr() as *const u8,
128 | std::mem::size_of::(),
129 | )
130 | };
131 | byte_slice
132 | }
133 |
134 | /// Converts the color to HSL color space.
135 | ///
136 | /// # Returns
137 | /// A new HslColor representing the same color in HSL space
138 | ///
139 | /// # Examples
140 | /// ```
141 | /// use pixel_loop::color::Color;
142 | ///
143 | /// let rgb = Color::from_rgb(255, 0, 0);
144 | /// let hsl = rgb.as_hsl();
145 | /// assert_eq!(hsl.h, 0.0); // Red has hue 0
146 | /// assert_eq!(hsl.s, 100.0); // Full saturation
147 | /// assert_eq!(hsl.l, 50.0); // Mid lightness
148 | /// ```
149 | pub fn as_hsl(&self) -> HslColor {
150 | // Taken and converted from: https://stackoverflow.com/a/9493060
151 | let r = self.r as f64 / 255.0;
152 | let g = self.g as f64 / 255.0;
153 | let b = self.b as f64 / 255.0;
154 | let vmax = r.max(g.max(b));
155 | let vmin = r.min(g.min(b));
156 | let l = (vmax + vmin) / 2.0;
157 |
158 | if vmax == vmin {
159 | return HslColor::new(0.0, 0.0, l); // achromatic
160 | }
161 |
162 | let d = vmax - vmin;
163 | let s = if l > 0.5 {
164 | d / (2.0 - vmax - vmin)
165 | } else {
166 | d / (vmax + vmin)
167 | };
168 |
169 | let mut h = (vmax + vmin) / 2.0;
170 |
171 | if vmax == r {
172 | h = (g - b) / d;
173 | if g < b {
174 | h += 6.0
175 | }
176 | }
177 |
178 | if vmax == g {
179 | h = (b - r) / d + 2.0;
180 | }
181 |
182 | if vmax == b {
183 | h = (r - g) / d + 4.0;
184 | }
185 |
186 | h /= 6.0;
187 |
188 | // The color conversion moves every value into the [0,1] number space.
189 | // But we want the hue in [0,360], s in [0,100] and l in [0,100]
190 | HslColor::new(h * 360f64, s * 100f64, l * 100f64)
191 | }
192 | }
193 |
194 | impl From for Color {
195 | fn from(v: HslColor) -> Self {
196 | // Taken and converted from: https://stackoverflow.com/a/9493060
197 |
198 | fn hue_to_rgb(p: f64, q: f64, t: f64) -> f64 {
199 | let mut t = t;
200 | if t < 0f64 {
201 | t += 1f64
202 | };
203 | if t > 1f64 {
204 | t -= 1f64
205 | };
206 | if t < 1f64 / 6f64 {
207 | return p + (q - p) * 6f64 * t;
208 | }
209 | if t < 1f64 / 2f64 {
210 | return q;
211 | }
212 | if t < 2f64 / 3f64 {
213 | return p + (q - p) * (2f64 / 3f64 - t) * 6f64;
214 | };
215 | p
216 | }
217 |
218 | let r;
219 | let g;
220 | let b;
221 |
222 | // The input for this algorithm expects all the h,s and l values in the
223 | // range [0,1].
224 | let h = v.h / 360f64;
225 | let s = v.s / 100f64;
226 | let l = v.l / 100f64;
227 |
228 | if s == 0.0 {
229 | r = l;
230 | g = l;
231 | b = l;
232 | } else {
233 | let q = if l < 0.5 {
234 | l * (1.0 + s)
235 | } else {
236 | l + s - l * s
237 | };
238 | let p = 2.0 * l - q;
239 |
240 | r = hue_to_rgb(p, q, h + 1f64 / 3f64);
241 | g = hue_to_rgb(p, q, h);
242 | b = hue_to_rgb(p, q, h - 1f64 / 3f64);
243 | }
244 | Color::from_rgb(
245 | (r * 255.0).round() as u8,
246 | (g * 255.0).round() as u8,
247 | (b * 255.0).round() as u8,
248 | )
249 | }
250 | }
251 |
252 | /// A color representation in HSL (Hue, Saturation, Lightness) color space.
253 | pub struct HslColor {
254 | /// Hue component [0-360]
255 | pub h: f64,
256 | /// Saturation component [0-100]
257 | pub s: f64,
258 | /// Lightness component [0-100]
259 | pub l: f64,
260 | }
261 |
262 | impl HslColor {
263 | /// Creates a new HslColor from HSL components.
264 | ///
265 | /// # Arguments
266 | /// * `h` - Hue [0-360]
267 | /// * `s` - Saturation [0-100]
268 | /// * `l` - Lightness [0-100]
269 | ///
270 | /// # Examples
271 | ///
272 | /// Initialize a pure red color:
273 | /// ```
274 | /// use pixel_loop::color::HslColor;
275 | ///
276 | /// let color = HslColor::new(0.0, 100.0, 50.0); // Pure red
277 | /// ```
278 | ///
279 | /// Convert from RGB to HSL:
280 | /// ```
281 | /// use pixel_loop::color::{Color, HslColor};
282 | /// let rgb = Color::from_rgb(255, 0, 0);
283 | /// let hsl = rgb.as_hsl();
284 | /// assert_eq!(hsl.h, 0.0); // Red has hue 0
285 | /// assert_eq!(hsl.s, 100.0); // Full saturation
286 | /// assert_eq!(hsl.l, 50.0); // Mid lightness
287 | /// ```
288 | /// Convert from HSL to RGB:
289 | /// ```
290 | /// use pixel_loop::color::{Color, HslColor};
291 | /// let hsl = HslColor::new(0.0, 100.0, 50.0);
292 | /// let rgb = Color::from(hsl);
293 | /// assert_eq!(rgb, Color::from_rgb(255, 0, 0)); // Pure red
294 | /// ```
295 | ///
296 | pub fn new(h: f64, s: f64, l: f64) -> Self {
297 | Self { h, s, l }
298 | }
299 | }
300 |
--------------------------------------------------------------------------------
/src/pixel_loop/input/pixels.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashSet;
2 |
3 | use winit::event::{Event, VirtualKeyCode};
4 |
5 | use crate::NextLoopState;
6 |
7 | use super::{InputState, KeyboardKey, KeyboardState};
8 |
9 | // Map winit keycodes to our KeyboardKey enum
10 | fn map_winit_key_to_pixel_loop(key: winit::event::VirtualKeyCode) -> Option {
11 | match key {
12 | // Alphanumeric
13 | VirtualKeyCode::Apostrophe => Some(KeyboardKey::Apostrophe),
14 | VirtualKeyCode::Comma => Some(KeyboardKey::Comma),
15 | VirtualKeyCode::Minus => Some(KeyboardKey::Minus),
16 | VirtualKeyCode::Period => Some(KeyboardKey::Period),
17 | VirtualKeyCode::Slash => Some(KeyboardKey::Slash),
18 | VirtualKeyCode::Key0 => Some(KeyboardKey::Zero),
19 | VirtualKeyCode::Key1 => Some(KeyboardKey::One),
20 | VirtualKeyCode::Key2 => Some(KeyboardKey::Two),
21 | VirtualKeyCode::Key3 => Some(KeyboardKey::Three),
22 | VirtualKeyCode::Key4 => Some(KeyboardKey::Four),
23 | VirtualKeyCode::Key5 => Some(KeyboardKey::Five),
24 | VirtualKeyCode::Key6 => Some(KeyboardKey::Six),
25 | VirtualKeyCode::Key7 => Some(KeyboardKey::Seven),
26 | VirtualKeyCode::Key8 => Some(KeyboardKey::Eight),
27 | VirtualKeyCode::Key9 => Some(KeyboardKey::Nine),
28 | VirtualKeyCode::Semicolon => Some(KeyboardKey::Semicolon),
29 | VirtualKeyCode::Equals => Some(KeyboardKey::Equal),
30 | VirtualKeyCode::A => Some(KeyboardKey::A),
31 | VirtualKeyCode::B => Some(KeyboardKey::B),
32 | VirtualKeyCode::C => Some(KeyboardKey::C),
33 | VirtualKeyCode::D => Some(KeyboardKey::D),
34 | VirtualKeyCode::E => Some(KeyboardKey::E),
35 | VirtualKeyCode::F => Some(KeyboardKey::F),
36 | VirtualKeyCode::G => Some(KeyboardKey::G),
37 | VirtualKeyCode::H => Some(KeyboardKey::H),
38 | VirtualKeyCode::I => Some(KeyboardKey::I),
39 | VirtualKeyCode::J => Some(KeyboardKey::J),
40 | VirtualKeyCode::K => Some(KeyboardKey::K),
41 | VirtualKeyCode::L => Some(KeyboardKey::L),
42 | VirtualKeyCode::M => Some(KeyboardKey::M),
43 | VirtualKeyCode::N => Some(KeyboardKey::N),
44 | VirtualKeyCode::O => Some(KeyboardKey::O),
45 | VirtualKeyCode::P => Some(KeyboardKey::P),
46 | VirtualKeyCode::Q => Some(KeyboardKey::Q),
47 | VirtualKeyCode::R => Some(KeyboardKey::R),
48 | VirtualKeyCode::S => Some(KeyboardKey::S),
49 | VirtualKeyCode::T => Some(KeyboardKey::T),
50 | VirtualKeyCode::U => Some(KeyboardKey::U),
51 | VirtualKeyCode::V => Some(KeyboardKey::V),
52 | VirtualKeyCode::W => Some(KeyboardKey::W),
53 | VirtualKeyCode::X => Some(KeyboardKey::X),
54 | VirtualKeyCode::Y => Some(KeyboardKey::Y),
55 | VirtualKeyCode::Z => Some(KeyboardKey::Z),
56 | VirtualKeyCode::LBracket => Some(KeyboardKey::LeftBracket),
57 | VirtualKeyCode::Backslash => Some(KeyboardKey::Backslash),
58 | VirtualKeyCode::RBracket => Some(KeyboardKey::RightBracket),
59 | VirtualKeyCode::Grave => Some(KeyboardKey::Grave),
60 |
61 | // Function keys
62 | VirtualKeyCode::Space => Some(KeyboardKey::Space),
63 | VirtualKeyCode::Escape => Some(KeyboardKey::Escape),
64 | VirtualKeyCode::Return => Some(KeyboardKey::Enter),
65 | VirtualKeyCode::Tab => Some(KeyboardKey::Tab),
66 | VirtualKeyCode::Back => Some(KeyboardKey::Backspace),
67 | VirtualKeyCode::Insert => Some(KeyboardKey::Insert),
68 | VirtualKeyCode::Delete => Some(KeyboardKey::Delete),
69 | VirtualKeyCode::Right => Some(KeyboardKey::Right),
70 | VirtualKeyCode::Left => Some(KeyboardKey::Left),
71 | VirtualKeyCode::Down => Some(KeyboardKey::Down),
72 | VirtualKeyCode::Up => Some(KeyboardKey::Up),
73 | VirtualKeyCode::PageUp => Some(KeyboardKey::PageUp),
74 | VirtualKeyCode::PageDown => Some(KeyboardKey::PageDown),
75 | VirtualKeyCode::Home => Some(KeyboardKey::Home),
76 | VirtualKeyCode::End => Some(KeyboardKey::End),
77 | VirtualKeyCode::Capital => Some(KeyboardKey::CapsLock),
78 | VirtualKeyCode::Scroll => Some(KeyboardKey::ScrollLock),
79 | VirtualKeyCode::Numlock => Some(KeyboardKey::NumLock),
80 | VirtualKeyCode::Snapshot => Some(KeyboardKey::PrintScreen),
81 | VirtualKeyCode::Pause => Some(KeyboardKey::Pause),
82 | VirtualKeyCode::F1 => Some(KeyboardKey::F1),
83 | VirtualKeyCode::F2 => Some(KeyboardKey::F2),
84 | VirtualKeyCode::F3 => Some(KeyboardKey::F3),
85 | VirtualKeyCode::F4 => Some(KeyboardKey::F4),
86 | VirtualKeyCode::F5 => Some(KeyboardKey::F5),
87 | VirtualKeyCode::F6 => Some(KeyboardKey::F6),
88 | VirtualKeyCode::F7 => Some(KeyboardKey::F7),
89 | VirtualKeyCode::F8 => Some(KeyboardKey::F8),
90 | VirtualKeyCode::F9 => Some(KeyboardKey::F9),
91 | VirtualKeyCode::F10 => Some(KeyboardKey::F10),
92 | VirtualKeyCode::F11 => Some(KeyboardKey::F11),
93 | VirtualKeyCode::F12 => Some(KeyboardKey::F12),
94 | VirtualKeyCode::LShift => Some(KeyboardKey::LeftShift),
95 | VirtualKeyCode::LControl => Some(KeyboardKey::LeftControl),
96 | VirtualKeyCode::LAlt => Some(KeyboardKey::LeftAlt),
97 | VirtualKeyCode::LWin => Some(KeyboardKey::LeftSuper),
98 | VirtualKeyCode::RShift => Some(KeyboardKey::RightShift),
99 | VirtualKeyCode::RControl => Some(KeyboardKey::RightControl),
100 | VirtualKeyCode::RAlt => Some(KeyboardKey::RightAlt),
101 | VirtualKeyCode::RWin => Some(KeyboardKey::RightSuper),
102 | VirtualKeyCode::Apps => Some(KeyboardKey::KbMenu),
103 |
104 | // Keypad
105 | VirtualKeyCode::Numpad0 => Some(KeyboardKey::Kp0),
106 | VirtualKeyCode::Numpad1 => Some(KeyboardKey::Kp1),
107 | VirtualKeyCode::Numpad2 => Some(KeyboardKey::Kp2),
108 | VirtualKeyCode::Numpad3 => Some(KeyboardKey::Kp3),
109 | VirtualKeyCode::Numpad4 => Some(KeyboardKey::Kp4),
110 | VirtualKeyCode::Numpad5 => Some(KeyboardKey::Kp5),
111 | VirtualKeyCode::Numpad6 => Some(KeyboardKey::Kp6),
112 | VirtualKeyCode::Numpad7 => Some(KeyboardKey::Kp7),
113 | VirtualKeyCode::Numpad8 => Some(KeyboardKey::Kp8),
114 | VirtualKeyCode::Numpad9 => Some(KeyboardKey::Kp9),
115 | VirtualKeyCode::NumpadDecimal => Some(KeyboardKey::KpDecimal),
116 | VirtualKeyCode::NumpadDivide => Some(KeyboardKey::KpDivide),
117 | VirtualKeyCode::NumpadMultiply => Some(KeyboardKey::KpMultiply),
118 | VirtualKeyCode::NumpadSubtract => Some(KeyboardKey::KpSubtract),
119 | VirtualKeyCode::NumpadAdd => Some(KeyboardKey::KpAdd),
120 | VirtualKeyCode::NumpadEnter => Some(KeyboardKey::KpEnter),
121 | VirtualKeyCode::NumpadEquals => Some(KeyboardKey::KpEqual),
122 |
123 | // Keys we don't map
124 | _ => None,
125 | }
126 | }
127 | pub struct PixelsInputState {
128 | keys_down: HashSet,
129 | keys_pressed_this_update: HashSet,
130 | keys_released_this_update: HashSet,
131 | clear_before_next_event: bool,
132 | }
133 |
134 | impl PixelsInputState {
135 | pub fn new() -> Self {
136 | Self {
137 | keys_down: HashSet::new(),
138 | keys_pressed_this_update: HashSet::new(),
139 | keys_released_this_update: HashSet::new(),
140 | clear_before_next_event: true,
141 | }
142 | }
143 |
144 | pub(crate) fn handle_new_event(&mut self, event: &Event<()>) {
145 | if self.clear_before_next_event {
146 | self.keys_pressed_this_update.clear();
147 | self.keys_released_this_update.clear();
148 | self.clear_before_next_event = false;
149 | }
150 |
151 | match event {
152 | Event::WindowEvent { event, .. } => match event {
153 | winit::event::WindowEvent::KeyboardInput {
154 | input:
155 | winit::event::KeyboardInput {
156 | state,
157 | virtual_keycode: Some(key),
158 | ..
159 | },
160 | ..
161 | } => {
162 | if *state == winit::event::ElementState::Pressed {
163 | if let Some(key) = map_winit_key_to_pixel_loop(*key) {
164 | if !self.keys_down.contains(&key) {
165 | self.keys_pressed_this_update.insert(key);
166 | }
167 | self.keys_down.insert(key);
168 | }
169 | } else {
170 | if let Some(key) = map_winit_key_to_pixel_loop(*key) {
171 | if self.keys_down.contains(&key) {
172 | self.keys_released_this_update.insert(key);
173 | }
174 | self.keys_down.remove(&key);
175 | }
176 | }
177 | }
178 | _ => {}
179 | },
180 | _ => {}
181 | }
182 | }
183 | }
184 |
185 | impl InputState for PixelsInputState {
186 | fn begin(&mut self) -> anyhow::Result<()> {
187 | // Nothing to do here
188 | Ok(())
189 | }
190 |
191 | fn next_loop(&mut self) -> anyhow::Result {
192 | self.clear_before_next_event = true;
193 | Ok(NextLoopState::Continue)
194 | }
195 |
196 | fn finish(&mut self) -> anyhow::Result<()> {
197 | // Nothing to do here
198 | Ok(())
199 | }
200 | }
201 |
202 | impl KeyboardState for PixelsInputState {
203 | fn is_key_pressed(&self, key: KeyboardKey) -> bool {
204 | self.keys_pressed_this_update.contains(&key)
205 | }
206 |
207 | fn is_key_down(&self, key: KeyboardKey) -> bool {
208 | self.keys_down.contains(&key)
209 | }
210 |
211 | fn is_key_released(&self, key: KeyboardKey) -> bool {
212 | self.keys_released_this_update.contains(&key)
213 | }
214 |
215 | fn is_key_up(&self, key: KeyboardKey) -> bool {
216 | !self.keys_down.contains(&key)
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/src/pixel_loop/canvas/pixels.rs:
--------------------------------------------------------------------------------
1 | //! Window-based canvas implementation using the pixels crate.
2 | //!
3 | //! This module provides a canvas implementation that renders directly to a window
4 | //! using the pixels crate for hardware-accelerated rendering. It requires the
5 | //! "pixels" feature to be enabled.
6 |
7 | use super::{Canvas, RenderableCanvas};
8 | use crate::color::{Color, ColorAsByteSlice};
9 | use crate::input::PixelsInputState;
10 | use crate::NextLoopState;
11 | use anyhow::{Context, Result};
12 | use pixels::{Pixels, SurfaceTexture};
13 | use std::ops::Range;
14 | use winit::dpi::LogicalSize;
15 | use winit::event::{Event, WindowEvent};
16 | use winit::event_loop::{ControlFlow, EventLoop};
17 | use winit::window::{Window, WindowBuilder};
18 | use winit_input_helper::WinitInputHelper;
19 |
20 | /// Context winit window-related resources.
21 | struct WinitContext {
22 | event_loop: EventLoop<()>,
23 | input_helper: WinitInputHelper,
24 | window: Window,
25 | }
26 |
27 | /// A canvas implementation that renders to a window using the pixels crate.
28 | ///
29 | /// This canvas provides hardware-accelerated rendering to a window surface
30 | /// using the pixels crate. It handles pixel data conversion between the
31 | /// internal Color type and the RGBA byte format required by pixels.
32 | ///
33 | /// # Example
34 | ///
35 | /// ```
36 | /// let canvas = PixelsCanvas::new(640, 480, "pixel loop", false)?;
37 | /// ```
38 | pub struct PixelsCanvas {
39 | /// The scale factor of the canvas supplied by the user to create a more
40 | /// "blocky" pixel feeling.
41 | user_scale_factor: u32,
42 | /// The winit window context
43 | context: Option,
44 | /// The underlying pixels instance for window rendering
45 | pixels: Pixels,
46 | /// The width of this canvas during the last loop
47 | last_loop_width: u32,
48 | /// The height of this canvas during the last loop
49 | last_loop_height: u32,
50 | }
51 |
52 | impl PixelsCanvas {
53 | /// Creates a new window-based canvas using the pixels crate as a backend.
54 | ///
55 | /// # Arguments
56 | /// * `width` - The width of the canvas in pixels
57 | /// * `height` - The height of the canvas in pixels
58 | /// * `scale_factor` - The scale factor of real window pixels to rendering canvas pixels
59 | /// * `title` - The title of the window
60 | /// * `resizable` - Whether the window should be resizable (This implies, that the pixel canvas size can change)
61 | pub fn new(
62 | width: u32,
63 | height: u32,
64 | scale_factor: Option,
65 | title: &str,
66 | resizable: bool,
67 | ) -> Result {
68 | let event_loop = EventLoop::new();
69 | let input_helper = WinitInputHelper::new();
70 | let window = {
71 | // This is the size, that we essentially want to use as window size,
72 | // if the screen is rendered at 100% scale.
73 | // It might not be the resolution, that is in the end actually
74 | // rendered.
75 | // And that may be again different from the size of the pixels
76 | // buffer, as this is scaled by the user supplied scale_factor as
77 | // well.
78 | let logical_window_size = LogicalSize::new(width as f64, height as f64);
79 | WindowBuilder::new()
80 | .with_title(title)
81 | .with_inner_size(logical_window_size)
82 | .with_min_inner_size(logical_window_size)
83 | .with_resizable(resizable)
84 | .build(&event_loop)?
85 | };
86 |
87 | let context = WinitContext {
88 | event_loop,
89 | input_helper,
90 | window,
91 | };
92 |
93 | // This is the actual size of the window in pixels, that is rendered.
94 | // Scaled by by the window.scale_factor
95 | let physical_dimensions = context.window.inner_size();
96 | let surface_texture = SurfaceTexture::new(
97 | physical_dimensions.width,
98 | physical_dimensions.height,
99 | &context.window,
100 | );
101 |
102 | // This is the size of the pixels buffer, that is based on the logical
103 | // (non system scaled) window size and the user supplied scale_factor
104 | let scaled_buffer_width = width / scale_factor.unwrap_or(1);
105 | let scaled_buffer_height = height / scale_factor.unwrap_or(1);
106 | let pixels = Pixels::new(scaled_buffer_width, scaled_buffer_height, surface_texture)
107 | .context("create pixels surface")?;
108 |
109 | Ok(Self {
110 | user_scale_factor: scale_factor.unwrap_or(1),
111 | context: Some(context),
112 | pixels,
113 | last_loop_height: 0, // Zero initialized to cause initial update
114 | last_loop_width: 0, // Zero initialized to cause initial update
115 | })
116 | }
117 | }
118 |
119 | impl PixelsCanvas {
120 | fn take_context(&mut self) -> WinitContext {
121 | self.context.take().unwrap()
122 | }
123 | }
124 |
125 | impl Canvas for PixelsCanvas {
126 | fn width(&self) -> u32 {
127 | self.pixels.texture().width()
128 | }
129 |
130 | fn height(&self) -> u32 {
131 | self.pixels.texture().height()
132 | }
133 |
134 | fn get_range(&self, range: Range) -> &[Color] {
135 | let byte_range = range.start * 4..range.end * 4;
136 | let buf = self.pixels.frame();
137 | let byte_slice = &buf[byte_range];
138 | Color::from_bytes(byte_slice)
139 | }
140 |
141 | fn set_range(&mut self, range: Range, colors: &[Color]) {
142 | let byte_range = range.start * 4..range.end * 4;
143 | let buf = self.pixels.frame_mut();
144 | buf[byte_range].copy_from_slice(colors.as_byte_slice())
145 | }
146 | }
147 |
148 | impl RenderableCanvas for PixelsCanvas {
149 | type Input = PixelsInputState;
150 |
151 | // @TODO: Move to input when handling mouse control there
152 | // fn physical_pos_to_canvas_pos(&self, x: f64, y: f64) -> Option<(u32, u32)> {
153 | // if let Ok((x, y)) = self.pixels.window_pos_to_pixel((x as f32, y as f32)) {
154 | // Some((x as u32, y as u32))
155 | // } else {
156 | // None
157 | // }
158 | // }
159 |
160 | fn render(&mut self) -> Result<()> {
161 | self.pixels
162 | .render()
163 | .context("letting pixels lib blit to screen")?;
164 | Ok(())
165 | }
166 |
167 | fn resize_surface(&mut self, width: u32, height: u32, window_scale_factor: Option) {
168 | self.pixels
169 | .resize_surface(width, height)
170 | .expect("to be able to resize surface");
171 |
172 | // First scale the display size by the window scale factor, then scale
173 | // by the user factor as well.
174 | let display_scaled_width = (width as f64 / window_scale_factor.unwrap_or(1.0)) as u32;
175 | let display_scaled_height = (height as f64 / window_scale_factor.unwrap_or(1.0)) as u32;
176 | let user_scaled_width = display_scaled_width / self.user_scale_factor;
177 | let user_scaled_height = display_scaled_height / self.user_scale_factor;
178 | self.pixels
179 | .resize_buffer(user_scaled_width, user_scaled_height)
180 | .expect("to be able to resize buffer");
181 | }
182 |
183 | /// Run the pixel loop, handling events and rendering.
184 | ///
185 | /// This implementation overrides the generic pixel_loop implementation, to
186 | /// handle the winit event_loop properly.
187 | fn run(mut pixel_loop: crate::PixelLoop) -> !
188 | where
189 | Self: Sized,
190 | {
191 | // We may take the context here, as we are never returning from this
192 | // function again.
193 | let context = pixel_loop.canvas.take_context();
194 |
195 | pixel_loop.begin().context("initialize pixel_loop").unwrap();
196 | let mut exit_code = 0i32;
197 | context.event_loop.run(move |event, _, control_flow| {
198 | pixel_loop.input_state.handle_new_event(&event);
199 | match event {
200 | Event::MainEventsCleared => {
201 | let next = pixel_loop
202 | .next_loop()
203 | .context("run next pixel loop")
204 | .unwrap();
205 | if let NextLoopState::Exit(code) = next {
206 | exit_code = code;
207 | *control_flow = ControlFlow::Exit;
208 | return;
209 | }
210 | // Track last communicated canvas size
211 | pixel_loop.canvas.last_loop_width = pixel_loop.canvas.width();
212 | pixel_loop.canvas.last_loop_height = pixel_loop.canvas.height();
213 | }
214 | Event::WindowEvent {
215 | event: win_event, ..
216 | } => match win_event {
217 | // Handle window resize events and correct buffer and
218 | // surface sizes
219 | WindowEvent::Resized(physical_size) => {
220 | pixel_loop.canvas.resize_surface(
221 | physical_size.width,
222 | physical_size.height,
223 | Some(context.window.scale_factor()),
224 | );
225 | }
226 | WindowEvent::CloseRequested => {
227 | exit_code = 0;
228 | *control_flow = ControlFlow::Exit;
229 | return;
230 | }
231 | _ => {}
232 | },
233 | Event::LoopDestroyed => {
234 | pixel_loop
235 | .finish(exit_code)
236 | .context("finish pixel loop")
237 | .unwrap();
238 | }
239 | _ => {}
240 | }
241 | });
242 | }
243 |
244 | fn did_resize(&self) -> Option<(u32, u32)> {
245 | if self.last_loop_width != self.width() || self.last_loop_height != self.height() {
246 | Some((self.width(), self.height()))
247 | } else {
248 | None
249 | }
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/pixel_loop/lib.rs:
--------------------------------------------------------------------------------
1 | //! Core game loop and engine functionality.
2 | //!
3 | //! This module provides the main game loop implementation with fixed timestep updates
4 | //! and rendering. It supports different input and canvas implementations through
5 | //! traits.
6 | //!
7 | //! # Example
8 | //! ```
9 | //! use pixel_loop::{run, EngineEnvironment};
10 | //! use pixel_loop::canvas::{Canvas, CrosstermCanvas, RenderableCanvas};
11 | //! use pixel_loop::color::Color;
12 | //! use pixel_loop::input::{CrosstermInputState, KeyboardKey, KeyboardState};
13 | //! use anyhow::Result;
14 | //!
15 | //! // Game state definition
16 | //! struct Box {
17 | //! position: (i64, i64),
18 | //! size: (u32, u32),
19 | //! color: Color,
20 | //! }
21 | //!
22 | //! struct State {
23 | //! box_entity: Box,
24 | //! }
25 | //!
26 | //! // Create initial state
27 | //! let state = State {
28 | //! box_entity: Box {
29 | //! position: (0, 0),
30 | //! size: (5, 5),
31 | //! color: Color::from_rgb(156, 80, 182),
32 | //! },
33 | //! };
34 | //!
35 | //! // Setup canvas and input
36 | //! let canvas = CrosstermCanvas::new();
37 | //! let input = CrosstermInputState::new();
38 | //!
39 | //! // Update function - called at fixed timestep
40 | //! fn update(env: &mut EngineEnvironment,
41 | //! state: &mut State,
42 | //! input: &CrosstermInputState,
43 | //! canvas: &mut CrosstermCanvas) -> Result<()> {
44 | //! // Handle input
45 | //! if input.is_key_down(KeyboardKey::Up) {
46 | //! state.box_entity.position.1 -= 1;
47 | //! }
48 | //! if input.is_key_down(KeyboardKey::Down) {
49 | //! state.box_entity.position.1 += 1;
50 | //! }
51 | //! Ok(())
52 | //! }
53 | //!
54 | //! // Render function - called as often as possible
55 | //! fn render(env: &mut EngineEnvironment,
56 | //! state: &mut State,
57 | //! input: &CrosstermInputState,
58 | //! canvas: &mut CrosstermCanvas,
59 | //! dt: std::time::Duration) -> Result<()> {
60 | //! canvas.clear_screen(&Color::from_rgb(0, 0, 0));
61 | //! canvas.filled_rect(
62 | //! state.box_entity.position.0,
63 | //! state.box_entity.position.1,
64 | //! state.box_entity.size.0,
65 | //! state.box_entity.size.1,
66 | //! &state.box_entity.color,
67 | //! );
68 | //! canvas.render()?;
69 | //! Ok(())
70 | //! }
71 | //!
72 | //! // Run the game loop
73 | //! run(60, state, input, canvas, update, render)?;
74 | //! Ok(())
75 | //! ```
76 |
77 | pub mod canvas;
78 | pub mod color;
79 | pub mod input;
80 |
81 | // Re-exporting deps for convenience in code using pixel_loop
82 | #[cfg(feature = "crossterm")]
83 | pub use crossterm;
84 | pub use rand;
85 | pub use rand_xoshiro;
86 |
87 | use anyhow::Result;
88 | use canvas::RenderableCanvas;
89 | use input::InputState;
90 | use rand::SeedableRng;
91 | use rand_xoshiro::Xoshiro256PlusPlus;
92 | use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
93 |
94 | /// Function type for the update step of the game loop.
95 | ///
96 | /// Called at a fixed timestep to update game state.
97 | ///
98 | /// # Arguments
99 | /// * `env` - Global engine environment containing shared resources
100 | /// * `state` - Mutable reference to the game state
101 | /// * `input` - Reference to the current input state
102 | /// * `canvas` - Mutable reference to the rendering canvas
103 | ///
104 | /// # Returns
105 | /// * `NextLoopState` - Determines if the game loop should continue or exit
106 | type UpdateFn = fn(
107 | &mut EngineEnvironment,
108 | &mut State,
109 | &::Input,
110 | &mut CanvasImpl,
111 | ) -> Result;
112 |
113 | /// Function type for the render step of the game loop.
114 | ///
115 | /// Called as often as possible with the actual frame time delta.
116 | ///
117 | /// # Arguments
118 | /// * `env` - Global engine environment containing shared resources
119 | /// * `state` - Mutable reference to the game state
120 | /// * `input` - Reference to the current input state
121 | /// * `canvas` - Mutable reference to the rendering canvas
122 | /// * `dt` - Time elapsed since last render
123 | ///
124 | /// # Returns
125 | /// * `NextLoopState` - Determines if the game loop should continue or exit
126 | type RenderFn = fn(
127 | &mut EngineEnvironment,
128 | &mut State,
129 | &::Input,
130 | &mut CanvasImpl,
131 | Duration,
132 | ) -> Result;
133 |
134 | /// Global engine state containing shared resources.
135 | ///
136 | /// Provides access to engine-wide functionality and resources that
137 | /// are available to both update and render functions.
138 | pub struct EngineEnvironment {
139 | /// Random number generator for game logic
140 | pub rand: Box,
141 | }
142 |
143 | impl Default for EngineEnvironment {
144 | fn default() -> Self {
145 | let micros = SystemTime::now()
146 | .duration_since(UNIX_EPOCH)
147 | .expect("If time since UNIX_EPOCH is 0 there is something wrong?")
148 | .as_micros();
149 | Self {
150 | rand: Box::new(Xoshiro256PlusPlus::seed_from_u64(micros as u64)),
151 | }
152 | }
153 | }
154 |
155 | /// Return type for the next loop state.
156 | /// Used to determine if the game loop should continue or exit.
157 | #[derive(Debug, Clone, Copy, PartialEq)]
158 | pub enum NextLoopState {
159 | Continue,
160 | Exit(i32),
161 | }
162 |
163 | /// Main game loop handler.
164 | ///
165 | /// Manages the game loop timing, state updates, and rendering.
166 | /// Uses a fixed timestep for updates while rendering as fast as possible.
167 | pub struct PixelLoop {
168 | accumulator: Duration,
169 | current_time: Instant,
170 | last_time: Instant,
171 | update_timestep: Duration,
172 | state: State,
173 | input_state: CanvasImpl::Input,
174 | engine_state: EngineEnvironment,
175 | canvas: CanvasImpl,
176 | update: UpdateFn,
177 | render: RenderFn,
178 | }
179 |
180 | impl PixelLoop
181 | where
182 | CanvasImpl: RenderableCanvas,
183 | {
184 | /// Creates a new game loop instance.
185 | ///
186 | /// # Arguments
187 | /// * `update_fps` - Target updates per second for the fixed timestep
188 | /// * `state` - Initial game state
189 | /// * `input_state` - Input handling implementation
190 | /// * `canvas` - Rendering canvas implementation
191 | /// * `update` - Update function called at fixed timestep
192 | /// * `render` - Render function called as often as possible
193 | ///
194 | /// # Panics
195 | /// If update_fps is 0
196 | pub fn new(
197 | update_fps: usize,
198 | state: State,
199 | input_state: CanvasImpl::Input,
200 | canvas: CanvasImpl,
201 | update: UpdateFn,
202 | render: RenderFn,
203 | ) -> Self {
204 | if update_fps == 0 {
205 | panic!("Designated FPS for updates needs to be > 0");
206 | }
207 |
208 | Self {
209 | accumulator: Duration::default(),
210 | current_time: Instant::now(),
211 | last_time: Instant::now(),
212 | update_timestep: Duration::from_nanos(
213 | (1_000_000_000f64 / update_fps as f64).round() as u64
214 | ),
215 | engine_state: EngineEnvironment::default(),
216 | state,
217 | input_state,
218 | canvas,
219 | update,
220 | render,
221 | }
222 | }
223 |
224 | /// Initializes the game loop.
225 | pub fn begin(&mut self) -> Result<()> {
226 | self.input_state.begin()?;
227 | self.canvas.begin()?;
228 | Ok(())
229 | }
230 |
231 | /// Processes the next frame of the game loop.
232 | pub fn next_loop(&mut self) -> Result {
233 | self.last_time = self.current_time;
234 | self.current_time = Instant::now();
235 | let mut dt = self.current_time - self.last_time;
236 |
237 | if dt > Duration::from_millis(100) {
238 | dt = Duration::from_millis(100);
239 | }
240 |
241 | while self.accumulator > self.update_timestep {
242 | let next = (self.input_state).next_loop()?;
243 | if let NextLoopState::Exit(..) = next {
244 | return Ok(next);
245 | };
246 |
247 | let next = (self.update)(
248 | &mut self.engine_state,
249 | &mut self.state,
250 | &self.input_state,
251 | &mut self.canvas,
252 | )?;
253 | if let NextLoopState::Exit(..) = next {
254 | return Ok(next);
255 | };
256 | self.accumulator -= self.update_timestep;
257 | }
258 |
259 | let next = (self.render)(
260 | &mut self.engine_state,
261 | &mut self.state,
262 | &self.input_state,
263 | &mut self.canvas,
264 | dt,
265 | )?;
266 | if let NextLoopState::Exit(..) = next {
267 | return Ok(next);
268 | };
269 |
270 | self.accumulator += dt;
271 | Ok(NextLoopState::Continue)
272 | }
273 |
274 | /// Cleans up resources when the game loop ends.
275 | pub fn finish(&mut self, code: i32) -> Result<()> {
276 | self.input_state.finish()?;
277 | self.canvas.finish(code)?;
278 | std::process::exit(code);
279 | }
280 | }
281 |
282 | /// Runs the game loop with the provided state and implementations.
283 | ///
284 | /// # Arguments
285 | /// * `updates_per_second` - Target rate for fixed timestep updates
286 | /// * `state` - Initial game state
287 | /// * `input_state` - Input handling implementation
288 | /// * `canvas` - Rendering canvas implementation
289 | /// * `update` - Update function called at fixed timestep
290 | /// * `render` - Render function called as often as possible
291 | ///
292 | /// # Errors
293 | /// Returns an error if initialization fails or if any update/render call fails
294 | pub fn run(
295 | updates_per_second: usize,
296 | state: State,
297 | input_state: CanvasImpl::Input,
298 | canvas: CanvasImpl,
299 | update: UpdateFn,
300 | render: RenderFn,
301 | ) -> ! {
302 | CanvasImpl::run(PixelLoop::new(
303 | updates_per_second,
304 | state,
305 | input_state,
306 | canvas,
307 | update,
308 | render,
309 | ))
310 | }
311 |
--------------------------------------------------------------------------------
/src/pixel_loop/canvas/mod.rs:
--------------------------------------------------------------------------------
1 | //! Canvas implementations for rendering pixels on different output targets
2 | //!
3 | //! A Canvas is a 2D grid of pixels that can be drawn to and rendered. It provides
4 | //! methods for setting and getting pixel colors, as well as blitting (copying) regions
5 | //! of pixels from one canvas to another.
6 | //!
7 | //! Furthermore it provides convenience methods for drawing certain shape
8 | //! primitives (eg. rectangles).
9 | //!
10 | //! It is the goto abstraction for rendering pixels in the pixel_loop library.
11 |
12 | #[cfg(feature = "crossterm")]
13 | pub mod crossterm;
14 | #[cfg(feature = "crossterm")]
15 | pub use crossterm::CrosstermCanvas;
16 |
17 | pub mod in_memory;
18 | pub use in_memory::InMemoryCanvas;
19 |
20 | #[cfg(feature = "pixels")]
21 | pub mod pixels;
22 | #[cfg(feature = "pixels")]
23 | pub use pixels::PixelsCanvas;
24 |
25 | use crate::color::Color;
26 | use crate::input::InputState;
27 | use crate::PixelLoop;
28 |
29 | use anyhow::Result;
30 | use std::ops::Range;
31 |
32 | /// Trait representing a basic canvas that can be drawn to.
33 | ///
34 | /// A canvas provides basic pixel manipulation operations and blitting capabilities
35 | /// for 2D graphics rendering as well as convenience methods for drawing shapes.
36 | ///
37 | /// Different implementations of this trait exist to utilize different rendering
38 | /// backends like an ansi terminal
39 | /// ([CrosstermCanvas](crate::canvas::crossterm::CrosstermCanvas)), or a window
40 | /// ([PixelsCanvas](crate::canvas::pixels::PixelsCanvas)).
41 | pub trait Canvas {
42 | /// Get the width of the canvas in pixels
43 | fn width(&self) -> u32;
44 | /// Get the height of the canvas in pixels
45 | fn height(&self) -> u32;
46 | /// Set a range of pixels to a given [Color]
47 | fn set_range(&mut self, range: Range, color: &[Color]);
48 | /// Get a range of pixels as a slice of [Color]s
49 | fn get_range(&self, range: Range) -> &[Color];
50 |
51 | /// Blit a full input canvas to this canvas instance at a given position,
52 | /// optionally tinting the input canvas with a color.
53 | ///
54 | /// # Arguments
55 | /// * `src_canvas` - The source canvas to blit from
56 | /// * `dst_x` - The x position to blit the source canvas to
57 | /// * `dst_y` - The y position to blit the source canvas to
58 | /// * `tint` - An optional color to tint the source canvas with
59 | fn blit(&mut self, src_canvas: &C, dst_x: i64, dst_y: i64, tint: Option<&Color>) {
60 | self.blit_rect(
61 | src_canvas,
62 | 0,
63 | 0,
64 | src_canvas.width(),
65 | src_canvas.height(),
66 | dst_x,
67 | dst_y,
68 | tint,
69 | )
70 | }
71 |
72 | /// Blit only a rectangular region of the input canvas to this canvas instance at a given position,
73 | /// optionally tinting the input canvas with a color.
74 | /// If the source rectangle is partially out of view, only the visible part will be blitted.
75 | /// If the destination rectangle is partially out of view, only the visible part will be blitted.
76 | ///
77 | /// See also: [blit_rect](crate::canvas::Canvas::blit_rect)
78 | fn blit_rect(
79 | &mut self,
80 | src_canvas: &C,
81 | src_x: u32,
82 | src_y: u32,
83 | width: u32,
84 | height: u32,
85 | dst_x: i64,
86 | dst_y: i64,
87 | tint: Option<&Color>,
88 | ) {
89 | if let Some((norm_dst_x, norm_dst_y, norm_width, norm_height)) =
90 | self.clip_rect(dst_x, dst_y, width, height)
91 | {
92 | for y in 0..norm_height {
93 | let src_start = (((src_y + y) * src_canvas.width()) + src_x) as usize;
94 | let src_end = src_start + u32::min(width, norm_width) as usize;
95 | let dst_start = (((norm_dst_y + y) * self.width()) + norm_dst_x) as usize;
96 | let dst_end = dst_start + norm_width as usize;
97 | let row = src_canvas.get_range(src_start..src_end);
98 |
99 | if let Some(tint) = tint {
100 | self.set_range(
101 | dst_start..dst_end,
102 | &row.iter()
103 | .map(|c| {
104 | Color::from_rgb(
105 | (c.r as usize * tint.r as usize / 255_usize) as u8,
106 | (c.g as usize * tint.g as usize / 255_usize) as u8,
107 | (c.b as usize * tint.b as usize / 255_usize) as u8,
108 | )
109 | })
110 | .collect::>(),
111 | );
112 | } else {
113 | self.set_range(dst_start..dst_end, row);
114 | }
115 | }
116 | }
117 | }
118 |
119 | /// Get the color of a specific pixel at a given position
120 | fn get(&self, x: u32, y: u32) -> &Color {
121 | let i = (y * self.width() + x) as usize;
122 | let color_slice = self.get_range(i..i + 1);
123 | // @TODO: Check if clone happens here
124 | &color_slice[0]
125 | }
126 |
127 | /// Get the color of a specific pixel at a given position, if it is in bounds of the canvas.
128 | ///
129 | /// # Returns
130 | /// * `Some(&Color)` - If the position is in bounds
131 | /// * `None` - If the position is out of bounds
132 | fn maybe_get(&self, x: i64, y: i64) -> Option<&Color> {
133 | if x < 0 || y < 0 || x >= self.width() as i64 || y >= self.height() as i64 {
134 | // Out of view
135 | None
136 | } else {
137 | Some(self.get(x as u32, y as u32))
138 | }
139 | }
140 |
141 | // @TODO: Not ideal: It would be better if the canvas knew its "empty"
142 | // (clear) color and could be asked `is_empty`.
143 | /// Check if a specific pixel at a given position is out of bounds (empty)
144 | /// or has the specified color.
145 | ///
146 | /// This method primarily can be used for pixel based collision detection.
147 | fn is_empty_or_color(&self, x: i64, y: i64, color: &Color) -> bool {
148 | self.maybe_get(x, y).map(|c| c == color).unwrap_or(true)
149 | }
150 |
151 | /// Set the color of a specific pixel at a given position
152 | fn set(&mut self, x: u32, y: u32, color: &Color) {
153 | let i = (y * self.width() + x) as usize;
154 | self.set_range(i..i + 1, std::slice::from_ref(color));
155 | }
156 |
157 | /// Clip a rectangle to the bounds of the canvas.
158 | ///
159 | /// # Returns
160 | /// * `Some((u32, u32, u32, u32))` - If the rectangle is partially or fully in view
161 | /// * `None` - If the rectangle is completely out of view
162 | fn clip_rect(&self, x: i64, y: i64, width: u32, height: u32) -> Option<(u32, u32, u32, u32)> {
163 | let width = width as i64;
164 | let height = height as i64;
165 | if x < -width || y < -height || x >= self.width() as i64 || y >= self.height() as i64 {
166 | // Completely out of view
167 | None
168 | } else {
169 | let norm_x = i64::max(0, x);
170 | let norm_y = i64::max(0, y);
171 | let norm_width = i64::min(width - (norm_x - x), self.width() as i64 - norm_x - 1);
172 | let norm_height = i64::min(height - (norm_y - y), self.height() as i64 - norm_y - 1);
173 | Some((
174 | norm_x as u32,
175 | norm_y as u32,
176 | norm_width as u32,
177 | norm_height as u32,
178 | ))
179 | }
180 | }
181 |
182 | /// Clear (fill) the whole canvas with a specific color
183 | fn clear_screen(&mut self, color: &Color) {
184 | self.filled_rect(0, 0, self.width(), self.height(), color)
185 | }
186 |
187 | /// Draw a filled rectangle at a given position with a given width and height
188 | fn filled_rect(&mut self, sx: i64, sy: i64, width: u32, height: u32, color: &Color) {
189 | if let Some((sx, sy, width, height)) = self.clip_rect(sx, sy, width, height) {
190 | let color_row = vec![*color; width as usize];
191 | for y in sy..sy + height {
192 | self.set_range(
193 | (y * self.width() + sx) as usize..(y * self.width() + sx + width) as usize,
194 | color_row.as_slice(),
195 | );
196 | }
197 | }
198 | }
199 | }
200 |
201 | /// Trait representing a canvas that can be rendered to a display target, like a
202 | /// window or terminal.
203 | ///
204 | /// Not necessarily all canvas implementations need to implement this trait, as
205 | /// there might be internally represented pixel data within a canvas, like
206 | /// [InMemoryCanvas](crate::canvas::in_memory::InMemoryCanvas), which can be
207 | /// used to hold loaded images.
208 | pub trait RenderableCanvas: Canvas {
209 | type Input: InputState;
210 |
211 | fn render(&mut self) -> Result<()>;
212 |
213 | /// Called when the surface of the canvas has been resized.
214 | /// The physical_dimensions of the new rendering surface are supplied.
215 | ///
216 | /// A call to this function happens when the window is resized, the terminal
217 | /// size changes, or when any other kind of render surface changes its size
218 | /// in any other way.
219 | ///
220 | /// The optional scale factor is the ratio of the physical dimensions
221 | /// supplied to the logical size of the surface based on the current
222 | /// rendering setup of the target system.
223 | fn resize_surface(&mut self, width: u32, height: u32, scale_factor: Option);
224 |
225 | // @TODO: Could this be generally handled/state set somehow within next_loop?
226 | /// Can be asked in an update loop, to check if the canvas resized since the last update loop.
227 | ///
228 | /// If it resized the new size is returned as a tuple. Otherwise None is returned.
229 | fn did_resize(&self) -> Option<(u32, u32)>;
230 |
231 | /// Main run loop for a pixel loop that renders to this canvas.
232 | ///
233 | /// A fully instantiated and configured pixel loop instance is provided to this method.
234 | ///
235 | /// A minimal implementation of this method would look like this:
236 | /// ```rust
237 | /// {
238 | /// pixel_loop.begin().expect("begin pixel_loop");
239 | /// loop {
240 | /// pixel_loop.next_loop().expect("next_loop pixel_loop");
241 | /// }
242 | /// }
243 | /// ```
244 | fn run(pixel_loop: PixelLoop) -> !
245 | where
246 | Self: Sized;
247 |
248 | /// Called before the main loop starts.
249 | fn begin(&mut self) -> Result<()> {
250 | Ok(())
251 | }
252 |
253 | /// Called after the main loop finishes.
254 | fn finish(&mut self, code: i32) -> Result<()> {
255 | Ok(())
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/examples/trivial_cli_demo/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "anyhow"
7 | version = "1.0.92"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13"
10 |
11 | [[package]]
12 | name = "autocfg"
13 | version = "1.4.0"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
16 |
17 | [[package]]
18 | name = "bitflags"
19 | version = "2.6.0"
20 | source = "registry+https://github.com/rust-lang/crates.io-index"
21 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
22 |
23 | [[package]]
24 | name = "byteorder"
25 | version = "1.5.0"
26 | source = "registry+https://github.com/rust-lang/crates.io-index"
27 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
28 |
29 | [[package]]
30 | name = "cfg-if"
31 | version = "1.0.0"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
34 |
35 | [[package]]
36 | name = "crossterm"
37 | version = "0.28.1"
38 | source = "registry+https://github.com/rust-lang/crates.io-index"
39 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
40 | dependencies = [
41 | "bitflags",
42 | "crossterm_winapi",
43 | "mio",
44 | "parking_lot",
45 | "rustix",
46 | "signal-hook",
47 | "signal-hook-mio",
48 | "winapi",
49 | ]
50 |
51 | [[package]]
52 | name = "crossterm_winapi"
53 | version = "0.9.1"
54 | source = "registry+https://github.com/rust-lang/crates.io-index"
55 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
56 | dependencies = [
57 | "winapi",
58 | ]
59 |
60 | [[package]]
61 | name = "errno"
62 | version = "0.3.9"
63 | source = "registry+https://github.com/rust-lang/crates.io-index"
64 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
65 | dependencies = [
66 | "libc",
67 | "windows-sys",
68 | ]
69 |
70 | [[package]]
71 | name = "getrandom"
72 | version = "0.2.15"
73 | source = "registry+https://github.com/rust-lang/crates.io-index"
74 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
75 | dependencies = [
76 | "cfg-if",
77 | "libc",
78 | "wasi",
79 | ]
80 |
81 | [[package]]
82 | name = "hermit-abi"
83 | version = "0.3.9"
84 | source = "registry+https://github.com/rust-lang/crates.io-index"
85 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
86 |
87 | [[package]]
88 | name = "libc"
89 | version = "0.2.161"
90 | source = "registry+https://github.com/rust-lang/crates.io-index"
91 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
92 |
93 | [[package]]
94 | name = "linux-raw-sys"
95 | version = "0.4.14"
96 | source = "registry+https://github.com/rust-lang/crates.io-index"
97 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
98 |
99 | [[package]]
100 | name = "lock_api"
101 | version = "0.4.12"
102 | source = "registry+https://github.com/rust-lang/crates.io-index"
103 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
104 | dependencies = [
105 | "autocfg",
106 | "scopeguard",
107 | ]
108 |
109 | [[package]]
110 | name = "log"
111 | version = "0.4.22"
112 | source = "registry+https://github.com/rust-lang/crates.io-index"
113 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
114 |
115 | [[package]]
116 | name = "mio"
117 | version = "1.0.2"
118 | source = "registry+https://github.com/rust-lang/crates.io-index"
119 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
120 | dependencies = [
121 | "hermit-abi",
122 | "libc",
123 | "log",
124 | "wasi",
125 | "windows-sys",
126 | ]
127 |
128 | [[package]]
129 | name = "parking_lot"
130 | version = "0.12.3"
131 | source = "registry+https://github.com/rust-lang/crates.io-index"
132 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
133 | dependencies = [
134 | "lock_api",
135 | "parking_lot_core",
136 | ]
137 |
138 | [[package]]
139 | name = "parking_lot_core"
140 | version = "0.9.10"
141 | source = "registry+https://github.com/rust-lang/crates.io-index"
142 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
143 | dependencies = [
144 | "cfg-if",
145 | "libc",
146 | "redox_syscall",
147 | "smallvec",
148 | "windows-targets",
149 | ]
150 |
151 | [[package]]
152 | name = "pixel_loop"
153 | version = "0.3.0"
154 | dependencies = [
155 | "anyhow",
156 | "crossterm",
157 | "rand",
158 | "rand_xoshiro",
159 | ]
160 |
161 | [[package]]
162 | name = "ppv-lite86"
163 | version = "0.2.20"
164 | source = "registry+https://github.com/rust-lang/crates.io-index"
165 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
166 | dependencies = [
167 | "zerocopy",
168 | ]
169 |
170 | [[package]]
171 | name = "proc-macro2"
172 | version = "1.0.89"
173 | source = "registry+https://github.com/rust-lang/crates.io-index"
174 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
175 | dependencies = [
176 | "unicode-ident",
177 | ]
178 |
179 | [[package]]
180 | name = "quote"
181 | version = "1.0.37"
182 | source = "registry+https://github.com/rust-lang/crates.io-index"
183 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
184 | dependencies = [
185 | "proc-macro2",
186 | ]
187 |
188 | [[package]]
189 | name = "rand"
190 | version = "0.8.5"
191 | source = "registry+https://github.com/rust-lang/crates.io-index"
192 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
193 | dependencies = [
194 | "libc",
195 | "rand_chacha",
196 | "rand_core",
197 | ]
198 |
199 | [[package]]
200 | name = "rand_chacha"
201 | version = "0.3.1"
202 | source = "registry+https://github.com/rust-lang/crates.io-index"
203 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
204 | dependencies = [
205 | "ppv-lite86",
206 | "rand_core",
207 | ]
208 |
209 | [[package]]
210 | name = "rand_core"
211 | version = "0.6.4"
212 | source = "registry+https://github.com/rust-lang/crates.io-index"
213 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
214 | dependencies = [
215 | "getrandom",
216 | ]
217 |
218 | [[package]]
219 | name = "rand_xoshiro"
220 | version = "0.6.0"
221 | source = "registry+https://github.com/rust-lang/crates.io-index"
222 | checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
223 | dependencies = [
224 | "rand_core",
225 | ]
226 |
227 | [[package]]
228 | name = "redox_syscall"
229 | version = "0.5.7"
230 | source = "registry+https://github.com/rust-lang/crates.io-index"
231 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
232 | dependencies = [
233 | "bitflags",
234 | ]
235 |
236 | [[package]]
237 | name = "rustix"
238 | version = "0.38.38"
239 | source = "registry+https://github.com/rust-lang/crates.io-index"
240 | checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a"
241 | dependencies = [
242 | "bitflags",
243 | "errno",
244 | "libc",
245 | "linux-raw-sys",
246 | "windows-sys",
247 | ]
248 |
249 | [[package]]
250 | name = "scopeguard"
251 | version = "1.2.0"
252 | source = "registry+https://github.com/rust-lang/crates.io-index"
253 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
254 |
255 | [[package]]
256 | name = "signal-hook"
257 | version = "0.3.17"
258 | source = "registry+https://github.com/rust-lang/crates.io-index"
259 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
260 | dependencies = [
261 | "libc",
262 | "signal-hook-registry",
263 | ]
264 |
265 | [[package]]
266 | name = "signal-hook-mio"
267 | version = "0.2.4"
268 | source = "registry+https://github.com/rust-lang/crates.io-index"
269 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
270 | dependencies = [
271 | "libc",
272 | "mio",
273 | "signal-hook",
274 | ]
275 |
276 | [[package]]
277 | name = "signal-hook-registry"
278 | version = "1.4.2"
279 | source = "registry+https://github.com/rust-lang/crates.io-index"
280 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
281 | dependencies = [
282 | "libc",
283 | ]
284 |
285 | [[package]]
286 | name = "smallvec"
287 | version = "1.13.2"
288 | source = "registry+https://github.com/rust-lang/crates.io-index"
289 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
290 |
291 | [[package]]
292 | name = "syn"
293 | version = "2.0.87"
294 | source = "registry+https://github.com/rust-lang/crates.io-index"
295 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
296 | dependencies = [
297 | "proc-macro2",
298 | "quote",
299 | "unicode-ident",
300 | ]
301 |
302 | [[package]]
303 | name = "trivial_cli_demo"
304 | version = "0.1.0"
305 | dependencies = [
306 | "anyhow",
307 | "pixel_loop",
308 | ]
309 |
310 | [[package]]
311 | name = "unicode-ident"
312 | version = "1.0.13"
313 | source = "registry+https://github.com/rust-lang/crates.io-index"
314 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
315 |
316 | [[package]]
317 | name = "wasi"
318 | version = "0.11.0+wasi-snapshot-preview1"
319 | source = "registry+https://github.com/rust-lang/crates.io-index"
320 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
321 |
322 | [[package]]
323 | name = "winapi"
324 | version = "0.3.9"
325 | source = "registry+https://github.com/rust-lang/crates.io-index"
326 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
327 | dependencies = [
328 | "winapi-i686-pc-windows-gnu",
329 | "winapi-x86_64-pc-windows-gnu",
330 | ]
331 |
332 | [[package]]
333 | name = "winapi-i686-pc-windows-gnu"
334 | version = "0.4.0"
335 | source = "registry+https://github.com/rust-lang/crates.io-index"
336 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
337 |
338 | [[package]]
339 | name = "winapi-x86_64-pc-windows-gnu"
340 | version = "0.4.0"
341 | source = "registry+https://github.com/rust-lang/crates.io-index"
342 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
343 |
344 | [[package]]
345 | name = "windows-sys"
346 | version = "0.52.0"
347 | source = "registry+https://github.com/rust-lang/crates.io-index"
348 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
349 | dependencies = [
350 | "windows-targets",
351 | ]
352 |
353 | [[package]]
354 | name = "windows-targets"
355 | version = "0.52.6"
356 | source = "registry+https://github.com/rust-lang/crates.io-index"
357 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
358 | dependencies = [
359 | "windows_aarch64_gnullvm",
360 | "windows_aarch64_msvc",
361 | "windows_i686_gnu",
362 | "windows_i686_gnullvm",
363 | "windows_i686_msvc",
364 | "windows_x86_64_gnu",
365 | "windows_x86_64_gnullvm",
366 | "windows_x86_64_msvc",
367 | ]
368 |
369 | [[package]]
370 | name = "windows_aarch64_gnullvm"
371 | version = "0.52.6"
372 | source = "registry+https://github.com/rust-lang/crates.io-index"
373 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
374 |
375 | [[package]]
376 | name = "windows_aarch64_msvc"
377 | version = "0.52.6"
378 | source = "registry+https://github.com/rust-lang/crates.io-index"
379 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
380 |
381 | [[package]]
382 | name = "windows_i686_gnu"
383 | version = "0.52.6"
384 | source = "registry+https://github.com/rust-lang/crates.io-index"
385 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
386 |
387 | [[package]]
388 | name = "windows_i686_gnullvm"
389 | version = "0.52.6"
390 | source = "registry+https://github.com/rust-lang/crates.io-index"
391 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
392 |
393 | [[package]]
394 | name = "windows_i686_msvc"
395 | version = "0.52.6"
396 | source = "registry+https://github.com/rust-lang/crates.io-index"
397 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
398 |
399 | [[package]]
400 | name = "windows_x86_64_gnu"
401 | version = "0.52.6"
402 | source = "registry+https://github.com/rust-lang/crates.io-index"
403 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
404 |
405 | [[package]]
406 | name = "windows_x86_64_gnullvm"
407 | version = "0.52.6"
408 | source = "registry+https://github.com/rust-lang/crates.io-index"
409 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
410 |
411 | [[package]]
412 | name = "windows_x86_64_msvc"
413 | version = "0.52.6"
414 | source = "registry+https://github.com/rust-lang/crates.io-index"
415 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
416 |
417 | [[package]]
418 | name = "zerocopy"
419 | version = "0.7.35"
420 | source = "registry+https://github.com/rust-lang/crates.io-index"
421 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
422 | dependencies = [
423 | "byteorder",
424 | "zerocopy-derive",
425 | ]
426 |
427 | [[package]]
428 | name = "zerocopy-derive"
429 | version = "0.7.35"
430 | source = "registry+https://github.com/rust-lang/crates.io-index"
431 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
432 | dependencies = [
433 | "proc-macro2",
434 | "quote",
435 | "syn",
436 | ]
437 |
--------------------------------------------------------------------------------
/src/pixel_loop/canvas/crossterm.rs:
--------------------------------------------------------------------------------
1 | //! Terminal-based canvas implementation using the crossterm crate.
2 | //!
3 | //! This module provides a canvas implementation that renders to the terminal
4 | //! using crossterm for colored output. It requires the "crossterm" feature
5 | //! to be enabled. The implementation uses Unicode half blocks for rendering
6 | //! and supports frame rate limiting.
7 |
8 | use super::{Canvas, RenderableCanvas};
9 | use crate::color::Color;
10 | use crate::input::CrosstermInputState;
11 | use anyhow::Result;
12 | use crossterm::event::Event;
13 | use crossterm::style::{self, Print, SetColors};
14 | use crossterm::{cursor, ExecutableCommand};
15 | use std::io::Write;
16 | use std::time::{Duration, Instant};
17 |
18 | /// A canvas implementation that renders to the terminal using crossterm.
19 | ///
20 | /// This canvas provides terminal-based rendering using Unicode half blocks
21 | /// and ANSI colors. It supports frame rate limiting and efficient updates
22 | /// by only redrawing changed parts of the screen.
23 | ///
24 | /// # Example
25 | /// ```
26 | /// use pixel_loop::canvas::CrosstermCanvas;
27 | /// use pixel_loop::color::*;
28 | /// use pixel_loop::canvas::Canvas;
29 | /// use pixel_loop::canvas::RenderableCanvas;
30 | /// use std::ops::Range;
31 | /// use anyhow::Result;
32 | ///
33 | /// fn main() -> Result<()> {
34 | /// let mut canvas = CrosstermCanvas::new(80, 24);
35 | /// canvas.filled_rect(5, 5, 10, 10, &Color::from_rgb(255, 0, 0));
36 | /// // Should of course be called within the [pixel_loop::run] function.
37 | /// canvas.render()?;
38 | /// Ok(())
39 | /// }
40 | /// ```
41 | pub struct CrosstermCanvas {
42 | /// Width of the canvas in pixels (characters)
43 | width: u32,
44 | /// Height of the canvas in pixels (half characters)
45 | height: u32,
46 | /// Resizability of the canvas
47 | resizable: bool,
48 | /// Current frame buffer
49 | buffer: Vec,
50 | /// Previous frame buffer for change detection
51 | previous_buffer: Vec,
52 | /// Minimal frame time in nanoseconds
53 | frame_limit_nanos: u64,
54 | /// Timestamp of the last rendered frame
55 | last_frame_time: Instant,
56 | /// The width of this canvas during the last loop
57 | last_loop_width: u32,
58 | /// The height of this canvas during the last loop
59 | last_loop_height: u32,
60 | }
61 |
62 | impl CrosstermCanvas {
63 | /// Creates a new terminal canvas automatically toking the size of the
64 | /// terminal it is spawned in.
65 | ///
66 | /// A canvas based on the terminals size is resizable by default.
67 | ///
68 | /// # Example
69 | /// ```
70 | /// use pixel_loop::canvas::CrosstermCanvas;
71 | ///
72 | /// let canvas = CrosstermCanvas::new();
73 | /// ```
74 | pub fn new() -> Self {
75 | let (columns, rows) = crossterm::terminal::size().unwrap();
76 | Self::new_with_size(columns as u32, rows as u32 * 2).with_resizable(true)
77 | }
78 |
79 | /// Creates a new terminal canvas with the specified dimensions.
80 | ///
81 | /// A canvas with specified dimensions is not resizable by default.
82 | ///
83 | /// # Arguments
84 | /// * `width` - The width of the canvas in characters
85 | /// * `height` - The height of the canvas in half characters
86 | ///
87 | /// # Example
88 | /// ```
89 | /// use pixel_loop::canvas::CrosstermCanvas;
90 | ///
91 | /// let canvas = CrosstermCanvas::new(80, 42);
92 | /// ```
93 | pub fn new_with_size(width: u32, height: u32) -> Self {
94 | let mut canvas = Self {
95 | width,
96 | height,
97 | resizable: false,
98 | buffer: vec![],
99 | previous_buffer: vec![],
100 | frame_limit_nanos: 1_000_000_000 / 60,
101 | last_frame_time: Instant::now(),
102 | last_loop_height: 0, // Zero initialized to cause initial update
103 | last_loop_width: 0, // Zero initialized to cause initial update
104 | };
105 | canvas.resize_surface(width, height, None);
106 | canvas
107 | }
108 |
109 | /// Sets the canvas to be resizable or not.
110 | pub fn with_resizable(mut self, resizable: bool) -> Self {
111 | self.resizable = resizable;
112 | self
113 | }
114 |
115 | /// Sets the frame rate limit.
116 | ///
117 | /// # Arguments
118 | /// * `limit` - Target frames per second
119 | ///
120 | /// By default, the canvas is limited to 60 frames per second.
121 | ///
122 | /// # Example
123 | /// ```
124 | /// use pixel_loop::canvas::CrosstermCanvas;
125 | ///
126 | /// // Limit the frame rate to 30 frames per second
127 | /// let mut canvas = CrosstermCanvas::new(80, 24).with_refresh_limit(30);
128 | /// ```
129 | pub fn with_refresh_limit(mut self, limit: usize) -> Self {
130 | self.frame_limit_nanos = 1_000_000_000u64 / limit as u64;
131 | self
132 | }
133 | }
134 |
135 | impl Canvas for CrosstermCanvas {
136 | fn width(&self) -> u32 {
137 | self.width as u32
138 | }
139 |
140 | fn height(&self) -> u32 {
141 | self.height as u32
142 | }
143 |
144 | fn set_range(&mut self, range: std::ops::Range, color: &[Color]) {
145 | self.buffer[range].copy_from_slice(color);
146 | }
147 |
148 | fn get_range(&self, range: std::ops::Range) -> &[Color] {
149 | &self.buffer[range]
150 | }
151 | }
152 |
153 | /// Unicode character representing the upper half block used for drawing half
154 | /// character height (quadratic) pixels.
155 | const UNICODE_UPPER_HALF_BLOCK: &str = "▀";
156 |
157 | /// Represents a region of the screen that needs to be updated.
158 | ///
159 | /// A patch contains the position and color data for a sequence of
160 | /// changed characters that can be efficiently written to the terminal.
161 | struct Patch {
162 | /// Terminal position (x, y)
163 | position: (u16, u16),
164 | /// Raw ANSI data to be written
165 | data: Vec,
166 | /// Previous colors for change detection
167 | previous_colors: Option<(Color, Color)>,
168 | }
169 |
170 | impl Patch {
171 | pub fn new(x: u16, y: u16) -> Self {
172 | Self {
173 | position: (x, y),
174 | data: Vec::new(),
175 | previous_colors: None,
176 | }
177 | }
178 |
179 | pub fn apply(&self, writer: &mut W) -> Result<()> {
180 | writer.execute(cursor::MoveTo(self.position.0, self.position.1))?;
181 | writer.write_all(&self.data)?;
182 | Ok(())
183 | }
184 |
185 | pub fn add_two_row_pixel(&mut self, upper: &Color, lower: &Color) -> Result<()> {
186 | if self.previous_colors.is_none()
187 | || self.previous_colors.as_ref().unwrap() != &(*upper, *lower)
188 | {
189 | self.data.execute(SetColors(style::Colors::new(
190 | style::Color::Rgb {
191 | r: upper.r,
192 | g: upper.g,
193 | b: upper.b,
194 | },
195 | style::Color::Rgb {
196 | r: lower.r,
197 | g: lower.g,
198 | b: lower.b,
199 | },
200 | )))?;
201 | self.previous_colors = Some((*upper, *lower));
202 | }
203 | self.data.execute(Print(UNICODE_UPPER_HALF_BLOCK))?;
204 | Ok(())
205 | }
206 | }
207 |
208 | impl CrosstermCanvas {
209 | fn calculate_patches(&self) -> Result> {
210 | let mut patches = Vec::new();
211 | let mut active_patch: Option = None;
212 |
213 | for y in (0..self.height as usize).step_by(2) {
214 | for x in 0..self.width as usize {
215 | let y1 = self.buffer[y * self.width as usize + x];
216 | let y2 = if self.height % 2 != 0 {
217 | Color::from_rgb(0, 0, 0)
218 | } else {
219 | self.buffer[(y + 1) * self.width as usize + x]
220 | };
221 |
222 | let py1 = self.previous_buffer[y * self.width as usize + x];
223 | let py2 = if self.height % 2 != 0 {
224 | Color::from_rgb(0, 0, 0)
225 | } else {
226 | self.previous_buffer[(y + 1) * self.width as usize + x]
227 | };
228 |
229 | if y1 != py1 || y2 != py2 {
230 | if active_patch.is_none() {
231 | active_patch = Some(Patch::new(x as u16, (y / 2) as u16));
232 | }
233 |
234 | let patch = active_patch.as_mut().unwrap();
235 | patch.add_two_row_pixel(&y1, &y2)?;
236 | } else if active_patch.is_some() {
237 | patches.push(active_patch.take().unwrap());
238 | }
239 | }
240 | if active_patch.is_some() {
241 | patches.push(active_patch.take().unwrap());
242 | }
243 | }
244 |
245 | if active_patch.is_some() {
246 | patches.push(active_patch.take().unwrap());
247 | }
248 |
249 | Ok(patches)
250 | }
251 |
252 | fn elapsed_since_last_frame(&self) -> u64 {
253 | // The return value of as_nanos is a u128, but a Duration from_nanos is
254 | // created with a u64. We are therefore casting this value into a u64 or
255 | // use the frame_limit_nanos as a default. Because if we are out of
256 | // limits (which shouldn't really happen), we need to directly rerender
257 | // anyways.
258 | self.last_frame_time
259 | .elapsed()
260 | .as_nanos()
261 | .try_into()
262 | .unwrap_or(self.frame_limit_nanos)
263 | }
264 |
265 | fn wait_for_next_frame(&mut self) {
266 | fn wait_half_using_thread_sleep(elapsed_nanos: u64, frame_limit_nanos: u64) {
267 | let minimum_thread_sleep_nanos = 4_000_000;
268 | if elapsed_nanos < frame_limit_nanos
269 | && (frame_limit_nanos - elapsed_nanos) / 2 > minimum_thread_sleep_nanos
270 | {
271 | std::thread::sleep(Duration::from_nanos(
272 | (frame_limit_nanos - elapsed_nanos) / 2,
273 | ));
274 | }
275 | }
276 | fn wait_using_spinlock(elapsed_nanos: u64, frame_limit_nanos: u64) {
277 | if elapsed_nanos < frame_limit_nanos {
278 | let wait_time = frame_limit_nanos - elapsed_nanos;
279 | let target_time = Instant::now() + Duration::from_nanos(wait_time);
280 | while Instant::now() < target_time {
281 | std::hint::spin_loop();
282 | }
283 | }
284 | }
285 | // Sleep the thread for have of the wait time needed.
286 | // Unfortunately sleeping the frame is quite impercise, therefore we
287 | // can't wait exactly the needed amount of time. We only wait for 1/2
288 | // of the time using a thread sleep.
289 | wait_half_using_thread_sleep(self.elapsed_since_last_frame(), self.frame_limit_nanos);
290 | // The rest of the time we precisely wait using a spinlock
291 | wait_using_spinlock(self.elapsed_since_last_frame(), self.frame_limit_nanos);
292 |
293 | self.last_frame_time = Instant::now();
294 | }
295 | }
296 |
297 | impl RenderableCanvas for CrosstermCanvas {
298 | type Input = CrosstermInputState;
299 |
300 | fn render(&mut self) -> anyhow::Result<()> {
301 | self.wait_for_next_frame();
302 |
303 | let mut stdout = std::io::stdout();
304 | let mut buffer = Vec::new();
305 |
306 | buffer.execute(cursor::Hide)?;
307 | let patches = self.calculate_patches()?;
308 | for patch in patches {
309 | patch.apply(&mut buffer)?;
310 | }
311 | buffer.execute(cursor::MoveTo(
312 | self.width.try_into()?,
313 | (self.height / 2).try_into()?,
314 | ))?;
315 | buffer.execute(cursor::Show)?;
316 | stdout.write_all(&buffer)?;
317 | stdout.flush()?;
318 |
319 | self.previous_buffer.copy_from_slice(&self.buffer);
320 |
321 | Ok(())
322 | }
323 |
324 | fn resize_surface(&mut self, width: u32, height: u32, scale_factor: Option) {
325 | self.width = width;
326 | self.height = height;
327 | self.buffer = vec![Color::from_rgb(0, 0, 0); width as usize * height as usize];
328 | self.previous_buffer = vec![Color::from_rgba(0, 0, 0, 0); width as usize * height as usize];
329 | }
330 |
331 | /// Runs the pixel loop.
332 | fn run(mut pixel_loop: crate::PixelLoop) -> ! {
333 | fn get_all_next_crossterm_events() -> Result> {
334 | use crossterm::event::{poll, read};
335 | let mut events = vec![];
336 | loop {
337 | if poll(Duration::from_secs(0))? {
338 | let event = read()?;
339 | events.push(event);
340 | } else {
341 | break;
342 | }
343 | }
344 |
345 | Ok(events)
346 | }
347 |
348 | pixel_loop.begin().expect("begin pixel_loop");
349 | loop {
350 | for event in get_all_next_crossterm_events().expect("get_all_next_crossterm_events") {
351 | // Handle resizeing of the terminal
352 | if let Event::Resize(columns, rows) = event {
353 | pixel_loop
354 | .canvas
355 | .resize_surface(columns as u32, rows as u32 * 2, None);
356 | }
357 |
358 | // Move elements to input state handler
359 | pixel_loop.input_state.handle_new_event(event);
360 | }
361 |
362 | let next = pixel_loop.next_loop().expect("next_loop pixel_loop");
363 | if let crate::NextLoopState::Exit(code) = next {
364 | pixel_loop.finish(code).expect("finish pixel loop");
365 | }
366 | // Track last communicated canvas size
367 | pixel_loop.canvas.last_loop_width = pixel_loop.canvas.width();
368 | pixel_loop.canvas.last_loop_height = pixel_loop.canvas.height();
369 | }
370 | }
371 |
372 | fn did_resize(&self) -> Option<(u32, u32)> {
373 | if self.last_loop_width != self.width() || self.last_loop_height != self.height() {
374 | Some((self.width(), self.height()))
375 | } else {
376 | None
377 | }
378 | }
379 |
380 | fn begin(&mut self) -> Result<()> {
381 | std::io::stdout().execute(crossterm::terminal::EnterAlternateScreen)?;
382 | Ok(())
383 | }
384 |
385 | fn finish(&mut self, _code: i32) -> Result<()> {
386 | std::io::stdout().execute(crossterm::terminal::LeaveAlternateScreen)?;
387 | Ok(())
388 | }
389 | }
390 |
--------------------------------------------------------------------------------
/src/pixel_loop/input/crossterm.rs:
--------------------------------------------------------------------------------
1 | //! Crossterm-based input handling implementation.
2 | //!
3 | //! This module provides a terminal input implementation using the crossterm crate.
4 | //! It supports both basic and enhanced keyboard input modes depending on terminal
5 | //! capabilities.
6 |
7 | use crate::NextLoopState;
8 |
9 | use super::{InputState, KeyboardKey, KeyboardState};
10 | use anyhow::Result;
11 | use crossterm::event::{
12 | Event, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
13 | };
14 | use crossterm::execute;
15 | use std::collections::{HashMap, HashSet};
16 |
17 | /// Input state handler for terminal input using crossterm.
18 | ///
19 | /// This implementation supports both basic and enhanced keyboard modes:
20 | /// - Enhanced mode provides accurate key press/release events if supported by the terminal
21 | /// - Basic mode falls back to simulated key releases based on timing
22 | pub struct CrosstermInputState {
23 | event_queue: Vec,
24 | keys_down: HashMap,
25 | keys_pressed_this_update: HashSet,
26 | keys_released_this_update: HashSet,
27 | event_cycles_before_released: usize,
28 | enhanced_keyboard: bool,
29 | }
30 |
31 | impl Default for CrosstermInputState {
32 | fn default() -> Self {
33 | Self::new()
34 | }
35 | }
36 |
37 | impl CrosstermInputState {
38 | /// Creates a new CrosstermInputState with default settings.
39 | ///
40 | /// # Example
41 | /// ```
42 | /// use pixel_loop::input::CrosstermInputState;
43 | ///
44 | /// let input_state = CrosstermInputState::new();
45 | /// ```
46 | pub fn new() -> Self {
47 | Self {
48 | event_queue: vec![],
49 | keys_down: HashMap::new(),
50 | keys_pressed_this_update: HashSet::new(),
51 | keys_released_this_update: HashSet::new(),
52 | event_cycles_before_released: 2,
53 | enhanced_keyboard: false,
54 | }
55 | }
56 |
57 | /// Sets the number of update cycles before a key is considered released
58 | /// in basic (non-enhanced) keyboard mode.
59 | ///
60 | /// This setting only affects terminals that don't support enhanced keyboard input.
61 | /// In basic mode, key releases are simulated after the specified number of
62 | /// update cycles since the last key press.
63 | ///
64 | /// # Arguments
65 | /// * `cycles` - Number of update cycles before a key is considered released
66 | ///
67 | /// The default value is 2 cycles.
68 | ///
69 | /// # Example
70 | /// ```
71 | /// use pixel_loop::input::CrosstermInputState;
72 | ///
73 | /// let input_state = CrosstermInputState::new()
74 | /// .with_event_cycles_before_released(3);
75 | /// ```
76 | pub fn with_event_cycles_before_released(self, cycles: usize) -> Self {
77 | Self {
78 | event_cycles_before_released: cycles,
79 | ..self
80 | }
81 | }
82 | }
83 |
84 | fn map_crossterm_keycode_to_pixel_loop(keycode: &crossterm::event::KeyCode) -> Option {
85 | use crossterm::event::KeyCode;
86 | match keycode {
87 | KeyCode::Backspace => Some(KeyboardKey::Backspace),
88 | KeyCode::Enter => Some(KeyboardKey::Enter),
89 | KeyCode::Left => Some(KeyboardKey::Left),
90 | KeyCode::Right => Some(KeyboardKey::Right),
91 | KeyCode::Up => Some(KeyboardKey::Up),
92 | KeyCode::Down => Some(KeyboardKey::Down),
93 | KeyCode::Home => Some(KeyboardKey::Home),
94 | KeyCode::End => Some(KeyboardKey::End),
95 | KeyCode::PageUp => Some(KeyboardKey::PageUp),
96 | KeyCode::PageDown => Some(KeyboardKey::PageDown),
97 | KeyCode::Tab => Some(KeyboardKey::Tab),
98 | KeyCode::BackTab => None,
99 | KeyCode::Delete => Some(KeyboardKey::Delete),
100 | KeyCode::Insert => Some(KeyboardKey::Insert),
101 | KeyCode::F(ref fkey) => match fkey {
102 | 1 => Some(KeyboardKey::F1),
103 | 2 => Some(KeyboardKey::F2),
104 | 3 => Some(KeyboardKey::F3),
105 | 4 => Some(KeyboardKey::F4),
106 | 5 => Some(KeyboardKey::F5),
107 | 6 => Some(KeyboardKey::F6),
108 | 7 => Some(KeyboardKey::F7),
109 | 8 => Some(KeyboardKey::F8),
110 | 9 => Some(KeyboardKey::F9),
111 | 10 => Some(KeyboardKey::F10),
112 | 11 => Some(KeyboardKey::F11),
113 | 12 => Some(KeyboardKey::F12),
114 | _ => None,
115 | },
116 | KeyCode::Char(ref character) => match character {
117 | '1' => Some(KeyboardKey::One),
118 | '2' => Some(KeyboardKey::Two),
119 | '3' => Some(KeyboardKey::Three),
120 | '4' => Some(KeyboardKey::Four),
121 | '5' => Some(KeyboardKey::Five),
122 | '6' => Some(KeyboardKey::Six),
123 | '7' => Some(KeyboardKey::Seven),
124 | '8' => Some(KeyboardKey::Eight),
125 | '9' => Some(KeyboardKey::Nine),
126 | '0' => Some(KeyboardKey::Zero),
127 | 'a' | 'A' => Some(KeyboardKey::A),
128 | 'b' | 'B' => Some(KeyboardKey::B),
129 | 'c' | 'C' => Some(KeyboardKey::C),
130 | 'd' | 'D' => Some(KeyboardKey::D),
131 | 'e' | 'E' => Some(KeyboardKey::E),
132 | 'f' | 'F' => Some(KeyboardKey::F),
133 | 'g' | 'G' => Some(KeyboardKey::G),
134 | 'h' | 'H' => Some(KeyboardKey::H),
135 | 'i' | 'I' => Some(KeyboardKey::I),
136 | 'j' | 'J' => Some(KeyboardKey::J),
137 | 'k' | 'K' => Some(KeyboardKey::K),
138 | 'l' | 'L' => Some(KeyboardKey::L),
139 | 'm' | 'M' => Some(KeyboardKey::M),
140 | 'n' | 'N' => Some(KeyboardKey::N),
141 | 'o' | 'O' => Some(KeyboardKey::O),
142 | 'p' | 'P' => Some(KeyboardKey::P),
143 | 'q' | 'Q' => Some(KeyboardKey::Q),
144 | 'r' | 'R' => Some(KeyboardKey::R),
145 | 's' | 'S' => Some(KeyboardKey::S),
146 | 't' | 'T' => Some(KeyboardKey::T),
147 | 'u' | 'U' => Some(KeyboardKey::U),
148 | 'v' | 'V' => Some(KeyboardKey::V),
149 | 'w' | 'W' => Some(KeyboardKey::W),
150 | 'x' | 'X' => Some(KeyboardKey::X),
151 | 'y' | 'Y' => Some(KeyboardKey::Y),
152 | 'z' | 'Z' => Some(KeyboardKey::Z),
153 | '\'' => Some(KeyboardKey::Apostrophe),
154 | ',' => Some(KeyboardKey::Comma),
155 | '-' => Some(KeyboardKey::Minus),
156 | '.' => Some(KeyboardKey::Period),
157 | '/' => Some(KeyboardKey::Slash),
158 | ';' => Some(KeyboardKey::Semicolon),
159 | '=' => Some(KeyboardKey::Equal),
160 | '[' => Some(KeyboardKey::LeftBracket),
161 | '\\' => Some(KeyboardKey::Backslash),
162 | ']' => Some(KeyboardKey::RightBracket),
163 | '`' => Some(KeyboardKey::Grave),
164 | ' ' => Some(KeyboardKey::Space),
165 | _ => None,
166 | },
167 | KeyCode::Null => None,
168 | KeyCode::Esc => Some(KeyboardKey::Escape),
169 | KeyCode::CapsLock => Some(KeyboardKey::CapsLock),
170 | KeyCode::ScrollLock => Some(KeyboardKey::ScrollLock),
171 | KeyCode::NumLock => Some(KeyboardKey::NumLock),
172 | KeyCode::PrintScreen => Some(KeyboardKey::PrintScreen),
173 | KeyCode::Pause => Some(KeyboardKey::Pause),
174 | KeyCode::Menu => Some(KeyboardKey::KbMenu),
175 | KeyCode::KeypadBegin => None,
176 | KeyCode::Media(_) => None,
177 | KeyCode::Modifier(_) => None, //@TODO: implement
178 | }
179 | }
180 |
181 | fn decrement_key_ref_counts(hmap: &mut HashMap) -> Vec {
182 | let mut removed_keys = vec![];
183 | // Shortcut if our length is 0. We are doing this, as this is mostly the
184 | // case, when no key is pressed. The hashmap iteration always has a
185 | // complexity of O(capacity) not O(len) due to internal implementation.
186 | if hmap.is_empty() {
187 | return removed_keys;
188 | }
189 |
190 | hmap.retain(|key, refcount: &mut usize| {
191 | if *refcount > 0 {
192 | *refcount -= 1;
193 | }
194 |
195 | if *refcount == 0 {
196 | removed_keys.push(*key);
197 | return false;
198 | }
199 |
200 | true
201 | });
202 |
203 | removed_keys
204 | }
205 |
206 | impl CrosstermInputState {
207 | pub(crate) fn handle_new_event(&mut self, event: Event) {
208 | self.event_queue.push(event);
209 | }
210 |
211 | fn take_all_queued_events(&mut self) -> Vec {
212 | self.event_queue.drain(..).collect()
213 | }
214 |
215 | fn next_loop_fallback(&mut self, next_events: Vec) -> Result<()> {
216 | use crossterm::event::{KeyEvent, KeyEventKind};
217 |
218 | let removed_keys_down = decrement_key_ref_counts(&mut self.keys_down);
219 | let keys_pressed_last_update = std::mem::take(&mut self.keys_pressed_this_update);
220 | let keys_released_last_update = std::mem::take(&mut self.keys_released_this_update);
221 |
222 | for event in next_events {
223 | match event {
224 | // Handle all pressed keys
225 | Event::Key(KeyEvent {
226 | kind: KeyEventKind::Press,
227 | ref code,
228 | ..
229 | }) => {
230 | if let Some(keyboard_key) = map_crossterm_keycode_to_pixel_loop(code) {
231 | // eprintln!("key DOWN handled {:?}", keyboard_key);
232 | if self
233 | .keys_down
234 | .insert(keyboard_key, self.event_cycles_before_released)
235 | .is_none()
236 | {
237 | // eprintln!("key PRESS handled {:?}", keyboard_key);
238 | // Key is newly inserted.
239 | self.keys_pressed_this_update.insert(keyboard_key);
240 | }
241 | } else {
242 | // eprintln!("Keypress NOT mapped");
243 | }
244 | }
245 | _ => {}
246 | }
247 | }
248 |
249 | // Fill keys, released this frame
250 | for removed_key in removed_keys_down {
251 | if !self.keys_down.contains_key(&removed_key) {
252 | // eprintln!("key RELEASE handled {:?}", removed_key);
253 | self.keys_released_this_update.insert(removed_key);
254 | }
255 | }
256 |
257 | Ok(())
258 | }
259 |
260 | fn next_loop_enhanced(&mut self, next_events: Vec) -> Result<()> {
261 | use crossterm::event::{KeyEvent, KeyEventKind};
262 |
263 | self.keys_pressed_this_update.drain();
264 | self.keys_released_this_update.drain();
265 |
266 | for event in next_events {
267 | match event {
268 | // Handle all pressed keys
269 | Event::Key(KeyEvent {
270 | ref kind, ref code, ..
271 | }) => {
272 | if let Some(keyboard_key) = map_crossterm_keycode_to_pixel_loop(code) {
273 | match kind {
274 | KeyEventKind::Press => {
275 | // eprintln!("KEY DOWN: {:?}", keyboard_key);
276 | if self
277 | .keys_down
278 | .insert(keyboard_key, self.event_cycles_before_released)
279 | .is_none()
280 | {
281 | // eprintln!("KEY PRESS: {:?}", keyboard_key);
282 | self.keys_pressed_this_update.insert(keyboard_key);
283 | }
284 | }
285 | KeyEventKind::Release => {
286 | // eprintln!("KEY UP: {:?}", keyboard_key);
287 | if self.keys_down.remove(&keyboard_key).is_some() {
288 | // eprintln!("KEY RELEASE: {:?}", keyboard_key);
289 | self.keys_released_this_update.insert(keyboard_key);
290 | }
291 | }
292 | KeyEventKind::Repeat => {
293 | // eprintln!("KEY REPEAT: {:?}", keyboard_key);
294 | // @TODO: Not handled yet. There isn't an API in hour trait for that (yet)
295 | }
296 | }
297 | }
298 | }
299 | _ => {}
300 | }
301 | }
302 |
303 | Ok(())
304 | }
305 | }
306 |
307 | impl InputState for CrosstermInputState {
308 | fn begin(&mut self) -> Result<()> {
309 | crossterm::terminal::enable_raw_mode()?;
310 | if crossterm::terminal::supports_keyboard_enhancement()? {
311 | // eprintln!("Enhanced Terminal YEAH!");
312 | self.enhanced_keyboard = true;
313 | execute!(
314 | std::io::stdout(),
315 | PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES)
316 | )?;
317 | } else {
318 | // eprintln!("No enhanced Terminal :_(");
319 | }
320 | Ok(())
321 | }
322 |
323 | fn finish(&mut self) -> Result<()> {
324 | if self.enhanced_keyboard {
325 | execute!(std::io::stdout(), PopKeyboardEnhancementFlags)?;
326 | self.enhanced_keyboard = false;
327 | }
328 | crossterm::terminal::disable_raw_mode()?;
329 | Ok(())
330 | }
331 |
332 | fn next_loop(&mut self) -> Result {
333 | use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
334 |
335 | let next_events = self.take_all_queued_events();
336 | for event in next_events.iter() {
337 | match event {
338 | // Handle Ctrl-C
339 | Event::Key(KeyEvent {
340 | kind: KeyEventKind::Press,
341 | code: KeyCode::Char('c') | KeyCode::Char('C'),
342 | modifiers: KeyModifiers::CONTROL,
343 | ..
344 | }) => {
345 | // SIGINT exitcode
346 | return Ok(NextLoopState::Exit(130));
347 | }
348 | _ => {}
349 | }
350 | }
351 |
352 | if self.enhanced_keyboard {
353 | self.next_loop_enhanced(next_events)?;
354 | } else {
355 | self.next_loop_fallback(next_events)?;
356 | }
357 |
358 | Ok(NextLoopState::Continue)
359 | }
360 | }
361 |
362 | impl KeyboardState for CrosstermInputState {
363 | fn is_key_pressed(&self, key: KeyboardKey) -> bool {
364 | self.keys_pressed_this_update.contains(&key)
365 | }
366 |
367 | fn is_key_down(&self, key: KeyboardKey) -> bool {
368 | self.keys_down.contains_key(&key)
369 | }
370 |
371 | fn is_key_released(&self, key: KeyboardKey) -> bool {
372 | self.keys_released_this_update.contains(&key)
373 | }
374 |
375 | fn is_key_up(&self, key: KeyboardKey) -> bool {
376 | !self.keys_down.contains_key(&key)
377 | }
378 | }
379 |
--------------------------------------------------------------------------------
/examples/trivial_pixels_demo/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "ab_glyph"
7 | version = "0.2.29"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0"
10 | dependencies = [
11 | "ab_glyph_rasterizer",
12 | "owned_ttf_parser",
13 | ]
14 |
15 | [[package]]
16 | name = "ab_glyph_rasterizer"
17 | version = "0.1.8"
18 | source = "registry+https://github.com/rust-lang/crates.io-index"
19 | checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
20 |
21 | [[package]]
22 | name = "addr2line"
23 | version = "0.24.2"
24 | source = "registry+https://github.com/rust-lang/crates.io-index"
25 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
26 | dependencies = [
27 | "gimli",
28 | ]
29 |
30 | [[package]]
31 | name = "adler2"
32 | version = "2.0.0"
33 | source = "registry+https://github.com/rust-lang/crates.io-index"
34 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
35 |
36 | [[package]]
37 | name = "ahash"
38 | version = "0.8.11"
39 | source = "registry+https://github.com/rust-lang/crates.io-index"
40 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
41 | dependencies = [
42 | "cfg-if",
43 | "once_cell",
44 | "version_check",
45 | "zerocopy",
46 | ]
47 |
48 | [[package]]
49 | name = "allocator-api2"
50 | version = "0.2.18"
51 | source = "registry+https://github.com/rust-lang/crates.io-index"
52 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
53 |
54 | [[package]]
55 | name = "android-activity"
56 | version = "0.4.3"
57 | source = "registry+https://github.com/rust-lang/crates.io-index"
58 | checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0"
59 | dependencies = [
60 | "android-properties",
61 | "bitflags 1.3.2",
62 | "cc",
63 | "jni-sys",
64 | "libc",
65 | "log",
66 | "ndk",
67 | "ndk-context",
68 | "ndk-sys",
69 | "num_enum 0.6.1",
70 | ]
71 |
72 | [[package]]
73 | name = "android-properties"
74 | version = "0.2.2"
75 | source = "registry+https://github.com/rust-lang/crates.io-index"
76 | checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
77 |
78 | [[package]]
79 | name = "android_system_properties"
80 | version = "0.1.5"
81 | source = "registry+https://github.com/rust-lang/crates.io-index"
82 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
83 | dependencies = [
84 | "libc",
85 | ]
86 |
87 | [[package]]
88 | name = "anyhow"
89 | version = "1.0.92"
90 | source = "registry+https://github.com/rust-lang/crates.io-index"
91 | checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13"
92 |
93 | [[package]]
94 | name = "arrayref"
95 | version = "0.3.9"
96 | source = "registry+https://github.com/rust-lang/crates.io-index"
97 | checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
98 |
99 | [[package]]
100 | name = "arrayvec"
101 | version = "0.7.6"
102 | source = "registry+https://github.com/rust-lang/crates.io-index"
103 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
104 |
105 | [[package]]
106 | name = "ash"
107 | version = "0.37.3+1.3.251"
108 | source = "registry+https://github.com/rust-lang/crates.io-index"
109 | checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
110 | dependencies = [
111 | "libloading 0.7.4",
112 | ]
113 |
114 | [[package]]
115 | name = "autocfg"
116 | version = "1.4.0"
117 | source = "registry+https://github.com/rust-lang/crates.io-index"
118 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
119 |
120 | [[package]]
121 | name = "backtrace"
122 | version = "0.3.74"
123 | source = "registry+https://github.com/rust-lang/crates.io-index"
124 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
125 | dependencies = [
126 | "addr2line",
127 | "cfg-if",
128 | "libc",
129 | "miniz_oxide",
130 | "object",
131 | "rustc-demangle",
132 | "windows-targets 0.52.6",
133 | ]
134 |
135 | [[package]]
136 | name = "bit-set"
137 | version = "0.5.3"
138 | source = "registry+https://github.com/rust-lang/crates.io-index"
139 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
140 | dependencies = [
141 | "bit-vec",
142 | ]
143 |
144 | [[package]]
145 | name = "bit-vec"
146 | version = "0.6.3"
147 | source = "registry+https://github.com/rust-lang/crates.io-index"
148 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
149 |
150 | [[package]]
151 | name = "bitflags"
152 | version = "1.3.2"
153 | source = "registry+https://github.com/rust-lang/crates.io-index"
154 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
155 |
156 | [[package]]
157 | name = "bitflags"
158 | version = "2.6.0"
159 | source = "registry+https://github.com/rust-lang/crates.io-index"
160 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
161 |
162 | [[package]]
163 | name = "block"
164 | version = "0.1.6"
165 | source = "registry+https://github.com/rust-lang/crates.io-index"
166 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
167 |
168 | [[package]]
169 | name = "block-sys"
170 | version = "0.1.0-beta.1"
171 | source = "registry+https://github.com/rust-lang/crates.io-index"
172 | checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146"
173 | dependencies = [
174 | "objc-sys",
175 | ]
176 |
177 | [[package]]
178 | name = "block2"
179 | version = "0.2.0-alpha.6"
180 | source = "registry+https://github.com/rust-lang/crates.io-index"
181 | checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42"
182 | dependencies = [
183 | "block-sys",
184 | "objc2-encode",
185 | ]
186 |
187 | [[package]]
188 | name = "bumpalo"
189 | version = "3.16.0"
190 | source = "registry+https://github.com/rust-lang/crates.io-index"
191 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
192 |
193 | [[package]]
194 | name = "bytemuck"
195 | version = "1.19.0"
196 | source = "registry+https://github.com/rust-lang/crates.io-index"
197 | checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
198 |
199 | [[package]]
200 | name = "byteorder"
201 | version = "1.5.0"
202 | source = "registry+https://github.com/rust-lang/crates.io-index"
203 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
204 |
205 | [[package]]
206 | name = "calloop"
207 | version = "0.10.6"
208 | source = "registry+https://github.com/rust-lang/crates.io-index"
209 | checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8"
210 | dependencies = [
211 | "bitflags 1.3.2",
212 | "log",
213 | "nix 0.25.1",
214 | "slotmap",
215 | "thiserror",
216 | "vec_map",
217 | ]
218 |
219 | [[package]]
220 | name = "cc"
221 | version = "1.1.34"
222 | source = "registry+https://github.com/rust-lang/crates.io-index"
223 | checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9"
224 | dependencies = [
225 | "jobserver",
226 | "libc",
227 | "shlex",
228 | ]
229 |
230 | [[package]]
231 | name = "cfg-if"
232 | version = "1.0.0"
233 | source = "registry+https://github.com/rust-lang/crates.io-index"
234 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
235 |
236 | [[package]]
237 | name = "cfg_aliases"
238 | version = "0.1.1"
239 | source = "registry+https://github.com/rust-lang/crates.io-index"
240 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
241 |
242 | [[package]]
243 | name = "codespan-reporting"
244 | version = "0.11.1"
245 | source = "registry+https://github.com/rust-lang/crates.io-index"
246 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
247 | dependencies = [
248 | "termcolor",
249 | "unicode-width",
250 | ]
251 |
252 | [[package]]
253 | name = "com-rs"
254 | version = "0.2.1"
255 | source = "registry+https://github.com/rust-lang/crates.io-index"
256 | checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642"
257 |
258 | [[package]]
259 | name = "core-foundation"
260 | version = "0.9.4"
261 | source = "registry+https://github.com/rust-lang/crates.io-index"
262 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
263 | dependencies = [
264 | "core-foundation-sys",
265 | "libc",
266 | ]
267 |
268 | [[package]]
269 | name = "core-foundation-sys"
270 | version = "0.8.7"
271 | source = "registry+https://github.com/rust-lang/crates.io-index"
272 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
273 |
274 | [[package]]
275 | name = "core-graphics"
276 | version = "0.22.3"
277 | source = "registry+https://github.com/rust-lang/crates.io-index"
278 | checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
279 | dependencies = [
280 | "bitflags 1.3.2",
281 | "core-foundation",
282 | "core-graphics-types",
283 | "foreign-types",
284 | "libc",
285 | ]
286 |
287 | [[package]]
288 | name = "core-graphics-types"
289 | version = "0.1.3"
290 | source = "registry+https://github.com/rust-lang/crates.io-index"
291 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
292 | dependencies = [
293 | "bitflags 1.3.2",
294 | "core-foundation",
295 | "libc",
296 | ]
297 |
298 | [[package]]
299 | name = "crc32fast"
300 | version = "1.4.2"
301 | source = "registry+https://github.com/rust-lang/crates.io-index"
302 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
303 | dependencies = [
304 | "cfg-if",
305 | ]
306 |
307 | [[package]]
308 | name = "d3d12"
309 | version = "0.6.0"
310 | source = "registry+https://github.com/rust-lang/crates.io-index"
311 | checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da"
312 | dependencies = [
313 | "bitflags 1.3.2",
314 | "libloading 0.7.4",
315 | "winapi",
316 | ]
317 |
318 | [[package]]
319 | name = "dispatch"
320 | version = "0.2.0"
321 | source = "registry+https://github.com/rust-lang/crates.io-index"
322 | checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
323 |
324 | [[package]]
325 | name = "dlib"
326 | version = "0.5.2"
327 | source = "registry+https://github.com/rust-lang/crates.io-index"
328 | checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
329 | dependencies = [
330 | "libloading 0.8.5",
331 | ]
332 |
333 | [[package]]
334 | name = "downcast-rs"
335 | version = "1.2.1"
336 | source = "registry+https://github.com/rust-lang/crates.io-index"
337 | checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
338 |
339 | [[package]]
340 | name = "equivalent"
341 | version = "1.0.1"
342 | source = "registry+https://github.com/rust-lang/crates.io-index"
343 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
344 |
345 | [[package]]
346 | name = "fdeflate"
347 | version = "0.3.6"
348 | source = "registry+https://github.com/rust-lang/crates.io-index"
349 | checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb"
350 | dependencies = [
351 | "simd-adler32",
352 | ]
353 |
354 | [[package]]
355 | name = "flate2"
356 | version = "1.0.34"
357 | source = "registry+https://github.com/rust-lang/crates.io-index"
358 | checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
359 | dependencies = [
360 | "crc32fast",
361 | "miniz_oxide",
362 | ]
363 |
364 | [[package]]
365 | name = "foreign-types"
366 | version = "0.3.2"
367 | source = "registry+https://github.com/rust-lang/crates.io-index"
368 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
369 | dependencies = [
370 | "foreign-types-shared",
371 | ]
372 |
373 | [[package]]
374 | name = "foreign-types-shared"
375 | version = "0.1.1"
376 | source = "registry+https://github.com/rust-lang/crates.io-index"
377 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
378 |
379 | [[package]]
380 | name = "getrandom"
381 | version = "0.2.15"
382 | source = "registry+https://github.com/rust-lang/crates.io-index"
383 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
384 | dependencies = [
385 | "cfg-if",
386 | "libc",
387 | "wasi",
388 | ]
389 |
390 | [[package]]
391 | name = "gimli"
392 | version = "0.31.1"
393 | source = "registry+https://github.com/rust-lang/crates.io-index"
394 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
395 |
396 | [[package]]
397 | name = "glow"
398 | version = "0.12.3"
399 | source = "registry+https://github.com/rust-lang/crates.io-index"
400 | checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728"
401 | dependencies = [
402 | "js-sys",
403 | "slotmap",
404 | "wasm-bindgen",
405 | "web-sys",
406 | ]
407 |
408 | [[package]]
409 | name = "gpu-alloc"
410 | version = "0.5.4"
411 | source = "registry+https://github.com/rust-lang/crates.io-index"
412 | checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62"
413 | dependencies = [
414 | "bitflags 1.3.2",
415 | "gpu-alloc-types",
416 | ]
417 |
418 | [[package]]
419 | name = "gpu-alloc-types"
420 | version = "0.2.0"
421 | source = "registry+https://github.com/rust-lang/crates.io-index"
422 | checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5"
423 | dependencies = [
424 | "bitflags 1.3.2",
425 | ]
426 |
427 | [[package]]
428 | name = "gpu-allocator"
429 | version = "0.22.0"
430 | source = "registry+https://github.com/rust-lang/crates.io-index"
431 | checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8"
432 | dependencies = [
433 | "backtrace",
434 | "log",
435 | "thiserror",
436 | "winapi",
437 | "windows",
438 | ]
439 |
440 | [[package]]
441 | name = "gpu-descriptor"
442 | version = "0.2.4"
443 | source = "registry+https://github.com/rust-lang/crates.io-index"
444 | checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c"
445 | dependencies = [
446 | "bitflags 2.6.0",
447 | "gpu-descriptor-types",
448 | "hashbrown 0.14.5",
449 | ]
450 |
451 | [[package]]
452 | name = "gpu-descriptor-types"
453 | version = "0.1.2"
454 | source = "registry+https://github.com/rust-lang/crates.io-index"
455 | checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c"
456 | dependencies = [
457 | "bitflags 2.6.0",
458 | ]
459 |
460 | [[package]]
461 | name = "hashbrown"
462 | version = "0.12.3"
463 | source = "registry+https://github.com/rust-lang/crates.io-index"
464 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
465 |
466 | [[package]]
467 | name = "hashbrown"
468 | version = "0.14.5"
469 | source = "registry+https://github.com/rust-lang/crates.io-index"
470 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
471 | dependencies = [
472 | "ahash",
473 | "allocator-api2",
474 | ]
475 |
476 | [[package]]
477 | name = "hashbrown"
478 | version = "0.15.0"
479 | source = "registry+https://github.com/rust-lang/crates.io-index"
480 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
481 |
482 | [[package]]
483 | name = "hassle-rs"
484 | version = "0.10.0"
485 | source = "registry+https://github.com/rust-lang/crates.io-index"
486 | checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0"
487 | dependencies = [
488 | "bitflags 1.3.2",
489 | "com-rs",
490 | "libc",
491 | "libloading 0.7.4",
492 | "thiserror",
493 | "widestring",
494 | "winapi",
495 | ]
496 |
497 | [[package]]
498 | name = "hexf-parse"
499 | version = "0.2.1"
500 | source = "registry+https://github.com/rust-lang/crates.io-index"
501 | checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
502 |
503 | [[package]]
504 | name = "indexmap"
505 | version = "1.9.3"
506 | source = "registry+https://github.com/rust-lang/crates.io-index"
507 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
508 | dependencies = [
509 | "autocfg",
510 | "hashbrown 0.12.3",
511 | ]
512 |
513 | [[package]]
514 | name = "indexmap"
515 | version = "2.6.0"
516 | source = "registry+https://github.com/rust-lang/crates.io-index"
517 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
518 | dependencies = [
519 | "equivalent",
520 | "hashbrown 0.15.0",
521 | ]
522 |
523 | [[package]]
524 | name = "instant"
525 | version = "0.1.13"
526 | source = "registry+https://github.com/rust-lang/crates.io-index"
527 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
528 | dependencies = [
529 | "cfg-if",
530 | "js-sys",
531 | "wasm-bindgen",
532 | "web-sys",
533 | ]
534 |
535 | [[package]]
536 | name = "jni-sys"
537 | version = "0.3.0"
538 | source = "registry+https://github.com/rust-lang/crates.io-index"
539 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
540 |
541 | [[package]]
542 | name = "jobserver"
543 | version = "0.1.32"
544 | source = "registry+https://github.com/rust-lang/crates.io-index"
545 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
546 | dependencies = [
547 | "libc",
548 | ]
549 |
550 | [[package]]
551 | name = "js-sys"
552 | version = "0.3.72"
553 | source = "registry+https://github.com/rust-lang/crates.io-index"
554 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
555 | dependencies = [
556 | "wasm-bindgen",
557 | ]
558 |
559 | [[package]]
560 | name = "khronos-egl"
561 | version = "4.1.0"
562 | source = "registry+https://github.com/rust-lang/crates.io-index"
563 | checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3"
564 | dependencies = [
565 | "libc",
566 | "libloading 0.7.4",
567 | "pkg-config",
568 | ]
569 |
570 | [[package]]
571 | name = "lazy_static"
572 | version = "1.5.0"
573 | source = "registry+https://github.com/rust-lang/crates.io-index"
574 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
575 |
576 | [[package]]
577 | name = "libc"
578 | version = "0.2.161"
579 | source = "registry+https://github.com/rust-lang/crates.io-index"
580 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
581 |
582 | [[package]]
583 | name = "libloading"
584 | version = "0.7.4"
585 | source = "registry+https://github.com/rust-lang/crates.io-index"
586 | checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
587 | dependencies = [
588 | "cfg-if",
589 | "winapi",
590 | ]
591 |
592 | [[package]]
593 | name = "libloading"
594 | version = "0.8.5"
595 | source = "registry+https://github.com/rust-lang/crates.io-index"
596 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
597 | dependencies = [
598 | "cfg-if",
599 | "windows-targets 0.52.6",
600 | ]
601 |
602 | [[package]]
603 | name = "libredox"
604 | version = "0.1.3"
605 | source = "registry+https://github.com/rust-lang/crates.io-index"
606 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
607 | dependencies = [
608 | "bitflags 2.6.0",
609 | "libc",
610 | "redox_syscall 0.5.7",
611 | ]
612 |
613 | [[package]]
614 | name = "lock_api"
615 | version = "0.4.12"
616 | source = "registry+https://github.com/rust-lang/crates.io-index"
617 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
618 | dependencies = [
619 | "autocfg",
620 | "scopeguard",
621 | ]
622 |
623 | [[package]]
624 | name = "log"
625 | version = "0.4.22"
626 | source = "registry+https://github.com/rust-lang/crates.io-index"
627 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
628 |
629 | [[package]]
630 | name = "malloc_buf"
631 | version = "0.0.6"
632 | source = "registry+https://github.com/rust-lang/crates.io-index"
633 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
634 | dependencies = [
635 | "libc",
636 | ]
637 |
638 | [[package]]
639 | name = "memchr"
640 | version = "2.7.4"
641 | source = "registry+https://github.com/rust-lang/crates.io-index"
642 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
643 |
644 | [[package]]
645 | name = "memmap2"
646 | version = "0.5.10"
647 | source = "registry+https://github.com/rust-lang/crates.io-index"
648 | checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
649 | dependencies = [
650 | "libc",
651 | ]
652 |
653 | [[package]]
654 | name = "memoffset"
655 | version = "0.6.5"
656 | source = "registry+https://github.com/rust-lang/crates.io-index"
657 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
658 | dependencies = [
659 | "autocfg",
660 | ]
661 |
662 | [[package]]
663 | name = "metal"
664 | version = "0.24.0"
665 | source = "registry+https://github.com/rust-lang/crates.io-index"
666 | checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060"
667 | dependencies = [
668 | "bitflags 1.3.2",
669 | "block",
670 | "core-graphics-types",
671 | "foreign-types",
672 | "log",
673 | "objc",
674 | ]
675 |
676 | [[package]]
677 | name = "miniz_oxide"
678 | version = "0.8.0"
679 | source = "registry+https://github.com/rust-lang/crates.io-index"
680 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
681 | dependencies = [
682 | "adler2",
683 | "simd-adler32",
684 | ]
685 |
686 | [[package]]
687 | name = "mio"
688 | version = "0.8.11"
689 | source = "registry+https://github.com/rust-lang/crates.io-index"
690 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
691 | dependencies = [
692 | "libc",
693 | "log",
694 | "wasi",
695 | "windows-sys 0.48.0",
696 | ]
697 |
698 | [[package]]
699 | name = "naga"
700 | version = "0.12.3"
701 | source = "registry+https://github.com/rust-lang/crates.io-index"
702 | checksum = "bbcc2e0513220fd2b598e6068608d4462db20322c0e77e47f6f488dfcfc279cb"
703 | dependencies = [
704 | "bit-set",
705 | "bitflags 1.3.2",
706 | "codespan-reporting",
707 | "hexf-parse",
708 | "indexmap 1.9.3",
709 | "log",
710 | "num-traits",
711 | "rustc-hash",
712 | "spirv",
713 | "termcolor",
714 | "thiserror",
715 | "unicode-xid",
716 | ]
717 |
718 | [[package]]
719 | name = "ndk"
720 | version = "0.7.0"
721 | source = "registry+https://github.com/rust-lang/crates.io-index"
722 | checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0"
723 | dependencies = [
724 | "bitflags 1.3.2",
725 | "jni-sys",
726 | "ndk-sys",
727 | "num_enum 0.5.11",
728 | "raw-window-handle",
729 | "thiserror",
730 | ]
731 |
732 | [[package]]
733 | name = "ndk-context"
734 | version = "0.1.1"
735 | source = "registry+https://github.com/rust-lang/crates.io-index"
736 | checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
737 |
738 | [[package]]
739 | name = "ndk-sys"
740 | version = "0.4.1+23.1.7779620"
741 | source = "registry+https://github.com/rust-lang/crates.io-index"
742 | checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3"
743 | dependencies = [
744 | "jni-sys",
745 | ]
746 |
747 | [[package]]
748 | name = "nix"
749 | version = "0.24.3"
750 | source = "registry+https://github.com/rust-lang/crates.io-index"
751 | checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
752 | dependencies = [
753 | "bitflags 1.3.2",
754 | "cfg-if",
755 | "libc",
756 | "memoffset",
757 | ]
758 |
759 | [[package]]
760 | name = "nix"
761 | version = "0.25.1"
762 | source = "registry+https://github.com/rust-lang/crates.io-index"
763 | checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
764 | dependencies = [
765 | "autocfg",
766 | "bitflags 1.3.2",
767 | "cfg-if",
768 | "libc",
769 | "memoffset",
770 | ]
771 |
772 | [[package]]
773 | name = "num-traits"
774 | version = "0.2.19"
775 | source = "registry+https://github.com/rust-lang/crates.io-index"
776 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
777 | dependencies = [
778 | "autocfg",
779 | ]
780 |
781 | [[package]]
782 | name = "num_enum"
783 | version = "0.5.11"
784 | source = "registry+https://github.com/rust-lang/crates.io-index"
785 | checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
786 | dependencies = [
787 | "num_enum_derive 0.5.11",
788 | ]
789 |
790 | [[package]]
791 | name = "num_enum"
792 | version = "0.6.1"
793 | source = "registry+https://github.com/rust-lang/crates.io-index"
794 | checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1"
795 | dependencies = [
796 | "num_enum_derive 0.6.1",
797 | ]
798 |
799 | [[package]]
800 | name = "num_enum_derive"
801 | version = "0.5.11"
802 | source = "registry+https://github.com/rust-lang/crates.io-index"
803 | checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
804 | dependencies = [
805 | "proc-macro-crate",
806 | "proc-macro2",
807 | "quote",
808 | "syn 1.0.109",
809 | ]
810 |
811 | [[package]]
812 | name = "num_enum_derive"
813 | version = "0.6.1"
814 | source = "registry+https://github.com/rust-lang/crates.io-index"
815 | checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
816 | dependencies = [
817 | "proc-macro-crate",
818 | "proc-macro2",
819 | "quote",
820 | "syn 2.0.87",
821 | ]
822 |
823 | [[package]]
824 | name = "objc"
825 | version = "0.2.7"
826 | source = "registry+https://github.com/rust-lang/crates.io-index"
827 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
828 | dependencies = [
829 | "malloc_buf",
830 | "objc_exception",
831 | ]
832 |
833 | [[package]]
834 | name = "objc-sys"
835 | version = "0.2.0-beta.2"
836 | source = "registry+https://github.com/rust-lang/crates.io-index"
837 | checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7"
838 |
839 | [[package]]
840 | name = "objc2"
841 | version = "0.3.0-beta.3.patch-leaks.3"
842 | source = "registry+https://github.com/rust-lang/crates.io-index"
843 | checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468"
844 | dependencies = [
845 | "block2",
846 | "objc-sys",
847 | "objc2-encode",
848 | ]
849 |
850 | [[package]]
851 | name = "objc2-encode"
852 | version = "2.0.0-pre.2"
853 | source = "registry+https://github.com/rust-lang/crates.io-index"
854 | checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512"
855 | dependencies = [
856 | "objc-sys",
857 | ]
858 |
859 | [[package]]
860 | name = "objc_exception"
861 | version = "0.1.2"
862 | source = "registry+https://github.com/rust-lang/crates.io-index"
863 | checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
864 | dependencies = [
865 | "cc",
866 | ]
867 |
868 | [[package]]
869 | name = "object"
870 | version = "0.36.5"
871 | source = "registry+https://github.com/rust-lang/crates.io-index"
872 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
873 | dependencies = [
874 | "memchr",
875 | ]
876 |
877 | [[package]]
878 | name = "once_cell"
879 | version = "1.20.2"
880 | source = "registry+https://github.com/rust-lang/crates.io-index"
881 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
882 |
883 | [[package]]
884 | name = "orbclient"
885 | version = "0.3.48"
886 | source = "registry+https://github.com/rust-lang/crates.io-index"
887 | checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43"
888 | dependencies = [
889 | "libredox",
890 | ]
891 |
892 | [[package]]
893 | name = "owned_ttf_parser"
894 | version = "0.25.0"
895 | source = "registry+https://github.com/rust-lang/crates.io-index"
896 | checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4"
897 | dependencies = [
898 | "ttf-parser",
899 | ]
900 |
901 | [[package]]
902 | name = "parking_lot"
903 | version = "0.12.3"
904 | source = "registry+https://github.com/rust-lang/crates.io-index"
905 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
906 | dependencies = [
907 | "lock_api",
908 | "parking_lot_core",
909 | ]
910 |
911 | [[package]]
912 | name = "parking_lot_core"
913 | version = "0.9.10"
914 | source = "registry+https://github.com/rust-lang/crates.io-index"
915 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
916 | dependencies = [
917 | "cfg-if",
918 | "libc",
919 | "redox_syscall 0.5.7",
920 | "smallvec",
921 | "windows-targets 0.52.6",
922 | ]
923 |
924 | [[package]]
925 | name = "percent-encoding"
926 | version = "2.3.1"
927 | source = "registry+https://github.com/rust-lang/crates.io-index"
928 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
929 |
930 | [[package]]
931 | name = "pixel_loop"
932 | version = "0.2.0"
933 | dependencies = [
934 | "anyhow",
935 | "pixels",
936 | "rand",
937 | "rand_xoshiro",
938 | "winit",
939 | "winit_input_helper",
940 | ]
941 |
942 | [[package]]
943 | name = "pixels"
944 | version = "0.13.0"
945 | source = "registry+https://github.com/rust-lang/crates.io-index"
946 | checksum = "8ba8189b31db4f12fbf0d4a8eab2d7d7343a504a8d8a7ea4b14ffb2e6129136a"
947 | dependencies = [
948 | "bytemuck",
949 | "pollster",
950 | "raw-window-handle",
951 | "thiserror",
952 | "ultraviolet",
953 | "wgpu",
954 | ]
955 |
956 | [[package]]
957 | name = "pkg-config"
958 | version = "0.3.31"
959 | source = "registry+https://github.com/rust-lang/crates.io-index"
960 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
961 |
962 | [[package]]
963 | name = "png"
964 | version = "0.17.14"
965 | source = "registry+https://github.com/rust-lang/crates.io-index"
966 | checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0"
967 | dependencies = [
968 | "bitflags 1.3.2",
969 | "crc32fast",
970 | "fdeflate",
971 | "flate2",
972 | "miniz_oxide",
973 | ]
974 |
975 | [[package]]
976 | name = "pollster"
977 | version = "0.3.0"
978 | source = "registry+https://github.com/rust-lang/crates.io-index"
979 | checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
980 |
981 | [[package]]
982 | name = "ppv-lite86"
983 | version = "0.2.20"
984 | source = "registry+https://github.com/rust-lang/crates.io-index"
985 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
986 | dependencies = [
987 | "zerocopy",
988 | ]
989 |
990 | [[package]]
991 | name = "proc-macro-crate"
992 | version = "1.3.1"
993 | source = "registry+https://github.com/rust-lang/crates.io-index"
994 | checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
995 | dependencies = [
996 | "once_cell",
997 | "toml_edit",
998 | ]
999 |
1000 | [[package]]
1001 | name = "proc-macro2"
1002 | version = "1.0.89"
1003 | source = "registry+https://github.com/rust-lang/crates.io-index"
1004 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
1005 | dependencies = [
1006 | "unicode-ident",
1007 | ]
1008 |
1009 | [[package]]
1010 | name = "profiling"
1011 | version = "1.0.16"
1012 | source = "registry+https://github.com/rust-lang/crates.io-index"
1013 | checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
1014 |
1015 | [[package]]
1016 | name = "quote"
1017 | version = "1.0.37"
1018 | source = "registry+https://github.com/rust-lang/crates.io-index"
1019 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
1020 | dependencies = [
1021 | "proc-macro2",
1022 | ]
1023 |
1024 | [[package]]
1025 | name = "rand"
1026 | version = "0.8.5"
1027 | source = "registry+https://github.com/rust-lang/crates.io-index"
1028 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1029 | dependencies = [
1030 | "libc",
1031 | "rand_chacha",
1032 | "rand_core",
1033 | ]
1034 |
1035 | [[package]]
1036 | name = "rand_chacha"
1037 | version = "0.3.1"
1038 | source = "registry+https://github.com/rust-lang/crates.io-index"
1039 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
1040 | dependencies = [
1041 | "ppv-lite86",
1042 | "rand_core",
1043 | ]
1044 |
1045 | [[package]]
1046 | name = "rand_core"
1047 | version = "0.6.4"
1048 | source = "registry+https://github.com/rust-lang/crates.io-index"
1049 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
1050 | dependencies = [
1051 | "getrandom",
1052 | ]
1053 |
1054 | [[package]]
1055 | name = "rand_xoshiro"
1056 | version = "0.6.0"
1057 | source = "registry+https://github.com/rust-lang/crates.io-index"
1058 | checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
1059 | dependencies = [
1060 | "rand_core",
1061 | ]
1062 |
1063 | [[package]]
1064 | name = "range-alloc"
1065 | version = "0.1.3"
1066 | source = "registry+https://github.com/rust-lang/crates.io-index"
1067 | checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
1068 |
1069 | [[package]]
1070 | name = "raw-window-handle"
1071 | version = "0.5.2"
1072 | source = "registry+https://github.com/rust-lang/crates.io-index"
1073 | checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
1074 |
1075 | [[package]]
1076 | name = "redox_syscall"
1077 | version = "0.3.5"
1078 | source = "registry+https://github.com/rust-lang/crates.io-index"
1079 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
1080 | dependencies = [
1081 | "bitflags 1.3.2",
1082 | ]
1083 |
1084 | [[package]]
1085 | name = "redox_syscall"
1086 | version = "0.5.7"
1087 | source = "registry+https://github.com/rust-lang/crates.io-index"
1088 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
1089 | dependencies = [
1090 | "bitflags 2.6.0",
1091 | ]
1092 |
1093 | [[package]]
1094 | name = "renderdoc-sys"
1095 | version = "1.1.0"
1096 | source = "registry+https://github.com/rust-lang/crates.io-index"
1097 | checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
1098 |
1099 | [[package]]
1100 | name = "rustc-demangle"
1101 | version = "0.1.24"
1102 | source = "registry+https://github.com/rust-lang/crates.io-index"
1103 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
1104 |
1105 | [[package]]
1106 | name = "rustc-hash"
1107 | version = "1.1.0"
1108 | source = "registry+https://github.com/rust-lang/crates.io-index"
1109 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
1110 |
1111 | [[package]]
1112 | name = "safe_arch"
1113 | version = "0.7.2"
1114 | source = "registry+https://github.com/rust-lang/crates.io-index"
1115 | checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a"
1116 | dependencies = [
1117 | "bytemuck",
1118 | ]
1119 |
1120 | [[package]]
1121 | name = "scoped-tls"
1122 | version = "1.0.1"
1123 | source = "registry+https://github.com/rust-lang/crates.io-index"
1124 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
1125 |
1126 | [[package]]
1127 | name = "scopeguard"
1128 | version = "1.2.0"
1129 | source = "registry+https://github.com/rust-lang/crates.io-index"
1130 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1131 |
1132 | [[package]]
1133 | name = "sctk-adwaita"
1134 | version = "0.5.4"
1135 | source = "registry+https://github.com/rust-lang/crates.io-index"
1136 | checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09"
1137 | dependencies = [
1138 | "ab_glyph",
1139 | "log",
1140 | "memmap2",
1141 | "smithay-client-toolkit",
1142 | "tiny-skia",
1143 | ]
1144 |
1145 | [[package]]
1146 | name = "shlex"
1147 | version = "1.3.0"
1148 | source = "registry+https://github.com/rust-lang/crates.io-index"
1149 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
1150 |
1151 | [[package]]
1152 | name = "simd-adler32"
1153 | version = "0.3.7"
1154 | source = "registry+https://github.com/rust-lang/crates.io-index"
1155 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
1156 |
1157 | [[package]]
1158 | name = "slotmap"
1159 | version = "1.0.7"
1160 | source = "registry+https://github.com/rust-lang/crates.io-index"
1161 | checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
1162 | dependencies = [
1163 | "version_check",
1164 | ]
1165 |
1166 | [[package]]
1167 | name = "smallvec"
1168 | version = "1.13.2"
1169 | source = "registry+https://github.com/rust-lang/crates.io-index"
1170 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
1171 |
1172 | [[package]]
1173 | name = "smithay-client-toolkit"
1174 | version = "0.16.1"
1175 | source = "registry+https://github.com/rust-lang/crates.io-index"
1176 | checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9"
1177 | dependencies = [
1178 | "bitflags 1.3.2",
1179 | "calloop",
1180 | "dlib",
1181 | "lazy_static",
1182 | "log",
1183 | "memmap2",
1184 | "nix 0.24.3",
1185 | "pkg-config",
1186 | "wayland-client",
1187 | "wayland-cursor",
1188 | "wayland-protocols",
1189 | ]
1190 |
1191 | [[package]]
1192 | name = "spirv"
1193 | version = "0.2.0+1.5.4"
1194 | source = "registry+https://github.com/rust-lang/crates.io-index"
1195 | checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830"
1196 | dependencies = [
1197 | "bitflags 1.3.2",
1198 | "num-traits",
1199 | ]
1200 |
1201 | [[package]]
1202 | name = "static_assertions"
1203 | version = "1.1.0"
1204 | source = "registry+https://github.com/rust-lang/crates.io-index"
1205 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1206 |
1207 | [[package]]
1208 | name = "strict-num"
1209 | version = "0.1.1"
1210 | source = "registry+https://github.com/rust-lang/crates.io-index"
1211 | checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
1212 |
1213 | [[package]]
1214 | name = "syn"
1215 | version = "1.0.109"
1216 | source = "registry+https://github.com/rust-lang/crates.io-index"
1217 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
1218 | dependencies = [
1219 | "proc-macro2",
1220 | "quote",
1221 | "unicode-ident",
1222 | ]
1223 |
1224 | [[package]]
1225 | name = "syn"
1226 | version = "2.0.87"
1227 | source = "registry+https://github.com/rust-lang/crates.io-index"
1228 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
1229 | dependencies = [
1230 | "proc-macro2",
1231 | "quote",
1232 | "unicode-ident",
1233 | ]
1234 |
1235 | [[package]]
1236 | name = "termcolor"
1237 | version = "1.4.1"
1238 | source = "registry+https://github.com/rust-lang/crates.io-index"
1239 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
1240 | dependencies = [
1241 | "winapi-util",
1242 | ]
1243 |
1244 | [[package]]
1245 | name = "thiserror"
1246 | version = "1.0.68"
1247 | source = "registry+https://github.com/rust-lang/crates.io-index"
1248 | checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892"
1249 | dependencies = [
1250 | "thiserror-impl",
1251 | ]
1252 |
1253 | [[package]]
1254 | name = "thiserror-impl"
1255 | version = "1.0.68"
1256 | source = "registry+https://github.com/rust-lang/crates.io-index"
1257 | checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
1258 | dependencies = [
1259 | "proc-macro2",
1260 | "quote",
1261 | "syn 2.0.87",
1262 | ]
1263 |
1264 | [[package]]
1265 | name = "tiny-skia"
1266 | version = "0.8.4"
1267 | source = "registry+https://github.com/rust-lang/crates.io-index"
1268 | checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67"
1269 | dependencies = [
1270 | "arrayref",
1271 | "arrayvec",
1272 | "bytemuck",
1273 | "cfg-if",
1274 | "png",
1275 | "tiny-skia-path",
1276 | ]
1277 |
1278 | [[package]]
1279 | name = "tiny-skia-path"
1280 | version = "0.8.4"
1281 | source = "registry+https://github.com/rust-lang/crates.io-index"
1282 | checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c"
1283 | dependencies = [
1284 | "arrayref",
1285 | "bytemuck",
1286 | "strict-num",
1287 | ]
1288 |
1289 | [[package]]
1290 | name = "toml_datetime"
1291 | version = "0.6.8"
1292 | source = "registry+https://github.com/rust-lang/crates.io-index"
1293 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
1294 |
1295 | [[package]]
1296 | name = "toml_edit"
1297 | version = "0.19.15"
1298 | source = "registry+https://github.com/rust-lang/crates.io-index"
1299 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
1300 | dependencies = [
1301 | "indexmap 2.6.0",
1302 | "toml_datetime",
1303 | "winnow",
1304 | ]
1305 |
1306 | [[package]]
1307 | name = "trivial_pixels_demo"
1308 | version = "0.1.0"
1309 | dependencies = [
1310 | "anyhow",
1311 | "pixel_loop",
1312 | ]
1313 |
1314 | [[package]]
1315 | name = "ttf-parser"
1316 | version = "0.25.0"
1317 | source = "registry+https://github.com/rust-lang/crates.io-index"
1318 | checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e"
1319 |
1320 | [[package]]
1321 | name = "ultraviolet"
1322 | version = "0.9.2"
1323 | source = "registry+https://github.com/rust-lang/crates.io-index"
1324 | checksum = "6a28554d13eb5daba527cc1b91b6c341372a0ae45ed277ffb2c6fbc04f319d7e"
1325 | dependencies = [
1326 | "wide",
1327 | ]
1328 |
1329 | [[package]]
1330 | name = "unicode-ident"
1331 | version = "1.0.13"
1332 | source = "registry+https://github.com/rust-lang/crates.io-index"
1333 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
1334 |
1335 | [[package]]
1336 | name = "unicode-width"
1337 | version = "0.1.14"
1338 | source = "registry+https://github.com/rust-lang/crates.io-index"
1339 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
1340 |
1341 | [[package]]
1342 | name = "unicode-xid"
1343 | version = "0.2.6"
1344 | source = "registry+https://github.com/rust-lang/crates.io-index"
1345 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
1346 |
1347 | [[package]]
1348 | name = "vec_map"
1349 | version = "0.8.2"
1350 | source = "registry+https://github.com/rust-lang/crates.io-index"
1351 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
1352 |
1353 | [[package]]
1354 | name = "version_check"
1355 | version = "0.9.5"
1356 | source = "registry+https://github.com/rust-lang/crates.io-index"
1357 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1358 |
1359 | [[package]]
1360 | name = "wasi"
1361 | version = "0.11.0+wasi-snapshot-preview1"
1362 | source = "registry+https://github.com/rust-lang/crates.io-index"
1363 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1364 |
1365 | [[package]]
1366 | name = "wasm-bindgen"
1367 | version = "0.2.95"
1368 | source = "registry+https://github.com/rust-lang/crates.io-index"
1369 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
1370 | dependencies = [
1371 | "cfg-if",
1372 | "once_cell",
1373 | "wasm-bindgen-macro",
1374 | ]
1375 |
1376 | [[package]]
1377 | name = "wasm-bindgen-backend"
1378 | version = "0.2.95"
1379 | source = "registry+https://github.com/rust-lang/crates.io-index"
1380 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
1381 | dependencies = [
1382 | "bumpalo",
1383 | "log",
1384 | "once_cell",
1385 | "proc-macro2",
1386 | "quote",
1387 | "syn 2.0.87",
1388 | "wasm-bindgen-shared",
1389 | ]
1390 |
1391 | [[package]]
1392 | name = "wasm-bindgen-futures"
1393 | version = "0.4.45"
1394 | source = "registry+https://github.com/rust-lang/crates.io-index"
1395 | checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
1396 | dependencies = [
1397 | "cfg-if",
1398 | "js-sys",
1399 | "wasm-bindgen",
1400 | "web-sys",
1401 | ]
1402 |
1403 | [[package]]
1404 | name = "wasm-bindgen-macro"
1405 | version = "0.2.95"
1406 | source = "registry+https://github.com/rust-lang/crates.io-index"
1407 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
1408 | dependencies = [
1409 | "quote",
1410 | "wasm-bindgen-macro-support",
1411 | ]
1412 |
1413 | [[package]]
1414 | name = "wasm-bindgen-macro-support"
1415 | version = "0.2.95"
1416 | source = "registry+https://github.com/rust-lang/crates.io-index"
1417 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
1418 | dependencies = [
1419 | "proc-macro2",
1420 | "quote",
1421 | "syn 2.0.87",
1422 | "wasm-bindgen-backend",
1423 | "wasm-bindgen-shared",
1424 | ]
1425 |
1426 | [[package]]
1427 | name = "wasm-bindgen-shared"
1428 | version = "0.2.95"
1429 | source = "registry+https://github.com/rust-lang/crates.io-index"
1430 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
1431 |
1432 | [[package]]
1433 | name = "wayland-client"
1434 | version = "0.29.5"
1435 | source = "registry+https://github.com/rust-lang/crates.io-index"
1436 | checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
1437 | dependencies = [
1438 | "bitflags 1.3.2",
1439 | "downcast-rs",
1440 | "libc",
1441 | "nix 0.24.3",
1442 | "scoped-tls",
1443 | "wayland-commons",
1444 | "wayland-scanner",
1445 | "wayland-sys",
1446 | ]
1447 |
1448 | [[package]]
1449 | name = "wayland-commons"
1450 | version = "0.29.5"
1451 | source = "registry+https://github.com/rust-lang/crates.io-index"
1452 | checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
1453 | dependencies = [
1454 | "nix 0.24.3",
1455 | "once_cell",
1456 | "smallvec",
1457 | "wayland-sys",
1458 | ]
1459 |
1460 | [[package]]
1461 | name = "wayland-cursor"
1462 | version = "0.29.5"
1463 | source = "registry+https://github.com/rust-lang/crates.io-index"
1464 | checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
1465 | dependencies = [
1466 | "nix 0.24.3",
1467 | "wayland-client",
1468 | "xcursor",
1469 | ]
1470 |
1471 | [[package]]
1472 | name = "wayland-protocols"
1473 | version = "0.29.5"
1474 | source = "registry+https://github.com/rust-lang/crates.io-index"
1475 | checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
1476 | dependencies = [
1477 | "bitflags 1.3.2",
1478 | "wayland-client",
1479 | "wayland-commons",
1480 | "wayland-scanner",
1481 | ]
1482 |
1483 | [[package]]
1484 | name = "wayland-scanner"
1485 | version = "0.29.5"
1486 | source = "registry+https://github.com/rust-lang/crates.io-index"
1487 | checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
1488 | dependencies = [
1489 | "proc-macro2",
1490 | "quote",
1491 | "xml-rs",
1492 | ]
1493 |
1494 | [[package]]
1495 | name = "wayland-sys"
1496 | version = "0.29.5"
1497 | source = "registry+https://github.com/rust-lang/crates.io-index"
1498 | checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
1499 | dependencies = [
1500 | "dlib",
1501 | "lazy_static",
1502 | "pkg-config",
1503 | ]
1504 |
1505 | [[package]]
1506 | name = "web-sys"
1507 | version = "0.3.72"
1508 | source = "registry+https://github.com/rust-lang/crates.io-index"
1509 | checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
1510 | dependencies = [
1511 | "js-sys",
1512 | "wasm-bindgen",
1513 | ]
1514 |
1515 | [[package]]
1516 | name = "wgpu"
1517 | version = "0.16.3"
1518 | source = "registry+https://github.com/rust-lang/crates.io-index"
1519 | checksum = "480c965c9306872eb6255fa55e4b4953be55a8b64d57e61d7ff840d3dcc051cd"
1520 | dependencies = [
1521 | "arrayvec",
1522 | "cfg-if",
1523 | "js-sys",
1524 | "log",
1525 | "naga",
1526 | "parking_lot",
1527 | "profiling",
1528 | "raw-window-handle",
1529 | "smallvec",
1530 | "static_assertions",
1531 | "wasm-bindgen",
1532 | "wasm-bindgen-futures",
1533 | "web-sys",
1534 | "wgpu-core",
1535 | "wgpu-hal",
1536 | "wgpu-types",
1537 | ]
1538 |
1539 | [[package]]
1540 | name = "wgpu-core"
1541 | version = "0.16.1"
1542 | source = "registry+https://github.com/rust-lang/crates.io-index"
1543 | checksum = "8f478237b4bf0d5b70a39898a66fa67ca3a007d79f2520485b8b0c3dfc46f8c2"
1544 | dependencies = [
1545 | "arrayvec",
1546 | "bit-vec",
1547 | "bitflags 2.6.0",
1548 | "codespan-reporting",
1549 | "log",
1550 | "naga",
1551 | "parking_lot",
1552 | "profiling",
1553 | "raw-window-handle",
1554 | "rustc-hash",
1555 | "smallvec",
1556 | "thiserror",
1557 | "web-sys",
1558 | "wgpu-hal",
1559 | "wgpu-types",
1560 | ]
1561 |
1562 | [[package]]
1563 | name = "wgpu-hal"
1564 | version = "0.16.2"
1565 | source = "registry+https://github.com/rust-lang/crates.io-index"
1566 | checksum = "1ecb3258078e936deee14fd4e0febe1cfe9bbb5ffef165cb60218d2ee5eb4448"
1567 | dependencies = [
1568 | "android_system_properties",
1569 | "arrayvec",
1570 | "ash",
1571 | "bit-set",
1572 | "bitflags 2.6.0",
1573 | "block",
1574 | "core-graphics-types",
1575 | "d3d12",
1576 | "foreign-types",
1577 | "glow",
1578 | "gpu-alloc",
1579 | "gpu-allocator",
1580 | "gpu-descriptor",
1581 | "hassle-rs",
1582 | "js-sys",
1583 | "khronos-egl",
1584 | "libc",
1585 | "libloading 0.8.5",
1586 | "log",
1587 | "metal",
1588 | "naga",
1589 | "objc",
1590 | "parking_lot",
1591 | "profiling",
1592 | "range-alloc",
1593 | "raw-window-handle",
1594 | "renderdoc-sys",
1595 | "rustc-hash",
1596 | "smallvec",
1597 | "thiserror",
1598 | "wasm-bindgen",
1599 | "web-sys",
1600 | "wgpu-types",
1601 | "winapi",
1602 | ]
1603 |
1604 | [[package]]
1605 | name = "wgpu-types"
1606 | version = "0.16.1"
1607 | source = "registry+https://github.com/rust-lang/crates.io-index"
1608 | checksum = "d0c153280bb108c2979eb5c7391cb18c56642dd3c072e55f52065e13e2a1252a"
1609 | dependencies = [
1610 | "bitflags 2.6.0",
1611 | "js-sys",
1612 | "web-sys",
1613 | ]
1614 |
1615 | [[package]]
1616 | name = "wide"
1617 | version = "0.7.28"
1618 | source = "registry+https://github.com/rust-lang/crates.io-index"
1619 | checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690"
1620 | dependencies = [
1621 | "bytemuck",
1622 | "safe_arch",
1623 | ]
1624 |
1625 | [[package]]
1626 | name = "widestring"
1627 | version = "1.1.0"
1628 | source = "registry+https://github.com/rust-lang/crates.io-index"
1629 | checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
1630 |
1631 | [[package]]
1632 | name = "winapi"
1633 | version = "0.3.9"
1634 | source = "registry+https://github.com/rust-lang/crates.io-index"
1635 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1636 | dependencies = [
1637 | "winapi-i686-pc-windows-gnu",
1638 | "winapi-x86_64-pc-windows-gnu",
1639 | ]
1640 |
1641 | [[package]]
1642 | name = "winapi-i686-pc-windows-gnu"
1643 | version = "0.4.0"
1644 | source = "registry+https://github.com/rust-lang/crates.io-index"
1645 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1646 |
1647 | [[package]]
1648 | name = "winapi-util"
1649 | version = "0.1.9"
1650 | source = "registry+https://github.com/rust-lang/crates.io-index"
1651 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
1652 | dependencies = [
1653 | "windows-sys 0.59.0",
1654 | ]
1655 |
1656 | [[package]]
1657 | name = "winapi-x86_64-pc-windows-gnu"
1658 | version = "0.4.0"
1659 | source = "registry+https://github.com/rust-lang/crates.io-index"
1660 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1661 |
1662 | [[package]]
1663 | name = "windows"
1664 | version = "0.44.0"
1665 | source = "registry+https://github.com/rust-lang/crates.io-index"
1666 | checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b"
1667 | dependencies = [
1668 | "windows-targets 0.42.2",
1669 | ]
1670 |
1671 | [[package]]
1672 | name = "windows-sys"
1673 | version = "0.45.0"
1674 | source = "registry+https://github.com/rust-lang/crates.io-index"
1675 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
1676 | dependencies = [
1677 | "windows-targets 0.42.2",
1678 | ]
1679 |
1680 | [[package]]
1681 | name = "windows-sys"
1682 | version = "0.48.0"
1683 | source = "registry+https://github.com/rust-lang/crates.io-index"
1684 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
1685 | dependencies = [
1686 | "windows-targets 0.48.5",
1687 | ]
1688 |
1689 | [[package]]
1690 | name = "windows-sys"
1691 | version = "0.59.0"
1692 | source = "registry+https://github.com/rust-lang/crates.io-index"
1693 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
1694 | dependencies = [
1695 | "windows-targets 0.52.6",
1696 | ]
1697 |
1698 | [[package]]
1699 | name = "windows-targets"
1700 | version = "0.42.2"
1701 | source = "registry+https://github.com/rust-lang/crates.io-index"
1702 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
1703 | dependencies = [
1704 | "windows_aarch64_gnullvm 0.42.2",
1705 | "windows_aarch64_msvc 0.42.2",
1706 | "windows_i686_gnu 0.42.2",
1707 | "windows_i686_msvc 0.42.2",
1708 | "windows_x86_64_gnu 0.42.2",
1709 | "windows_x86_64_gnullvm 0.42.2",
1710 | "windows_x86_64_msvc 0.42.2",
1711 | ]
1712 |
1713 | [[package]]
1714 | name = "windows-targets"
1715 | version = "0.48.5"
1716 | source = "registry+https://github.com/rust-lang/crates.io-index"
1717 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
1718 | dependencies = [
1719 | "windows_aarch64_gnullvm 0.48.5",
1720 | "windows_aarch64_msvc 0.48.5",
1721 | "windows_i686_gnu 0.48.5",
1722 | "windows_i686_msvc 0.48.5",
1723 | "windows_x86_64_gnu 0.48.5",
1724 | "windows_x86_64_gnullvm 0.48.5",
1725 | "windows_x86_64_msvc 0.48.5",
1726 | ]
1727 |
1728 | [[package]]
1729 | name = "windows-targets"
1730 | version = "0.52.6"
1731 | source = "registry+https://github.com/rust-lang/crates.io-index"
1732 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
1733 | dependencies = [
1734 | "windows_aarch64_gnullvm 0.52.6",
1735 | "windows_aarch64_msvc 0.52.6",
1736 | "windows_i686_gnu 0.52.6",
1737 | "windows_i686_gnullvm",
1738 | "windows_i686_msvc 0.52.6",
1739 | "windows_x86_64_gnu 0.52.6",
1740 | "windows_x86_64_gnullvm 0.52.6",
1741 | "windows_x86_64_msvc 0.52.6",
1742 | ]
1743 |
1744 | [[package]]
1745 | name = "windows_aarch64_gnullvm"
1746 | version = "0.42.2"
1747 | source = "registry+https://github.com/rust-lang/crates.io-index"
1748 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
1749 |
1750 | [[package]]
1751 | name = "windows_aarch64_gnullvm"
1752 | version = "0.48.5"
1753 | source = "registry+https://github.com/rust-lang/crates.io-index"
1754 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
1755 |
1756 | [[package]]
1757 | name = "windows_aarch64_gnullvm"
1758 | version = "0.52.6"
1759 | source = "registry+https://github.com/rust-lang/crates.io-index"
1760 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
1761 |
1762 | [[package]]
1763 | name = "windows_aarch64_msvc"
1764 | version = "0.42.2"
1765 | source = "registry+https://github.com/rust-lang/crates.io-index"
1766 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
1767 |
1768 | [[package]]
1769 | name = "windows_aarch64_msvc"
1770 | version = "0.48.5"
1771 | source = "registry+https://github.com/rust-lang/crates.io-index"
1772 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
1773 |
1774 | [[package]]
1775 | name = "windows_aarch64_msvc"
1776 | version = "0.52.6"
1777 | source = "registry+https://github.com/rust-lang/crates.io-index"
1778 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
1779 |
1780 | [[package]]
1781 | name = "windows_i686_gnu"
1782 | version = "0.42.2"
1783 | source = "registry+https://github.com/rust-lang/crates.io-index"
1784 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
1785 |
1786 | [[package]]
1787 | name = "windows_i686_gnu"
1788 | version = "0.48.5"
1789 | source = "registry+https://github.com/rust-lang/crates.io-index"
1790 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
1791 |
1792 | [[package]]
1793 | name = "windows_i686_gnu"
1794 | version = "0.52.6"
1795 | source = "registry+https://github.com/rust-lang/crates.io-index"
1796 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
1797 |
1798 | [[package]]
1799 | name = "windows_i686_gnullvm"
1800 | version = "0.52.6"
1801 | source = "registry+https://github.com/rust-lang/crates.io-index"
1802 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
1803 |
1804 | [[package]]
1805 | name = "windows_i686_msvc"
1806 | version = "0.42.2"
1807 | source = "registry+https://github.com/rust-lang/crates.io-index"
1808 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
1809 |
1810 | [[package]]
1811 | name = "windows_i686_msvc"
1812 | version = "0.48.5"
1813 | source = "registry+https://github.com/rust-lang/crates.io-index"
1814 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
1815 |
1816 | [[package]]
1817 | name = "windows_i686_msvc"
1818 | version = "0.52.6"
1819 | source = "registry+https://github.com/rust-lang/crates.io-index"
1820 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
1821 |
1822 | [[package]]
1823 | name = "windows_x86_64_gnu"
1824 | version = "0.42.2"
1825 | source = "registry+https://github.com/rust-lang/crates.io-index"
1826 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
1827 |
1828 | [[package]]
1829 | name = "windows_x86_64_gnu"
1830 | version = "0.48.5"
1831 | source = "registry+https://github.com/rust-lang/crates.io-index"
1832 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
1833 |
1834 | [[package]]
1835 | name = "windows_x86_64_gnu"
1836 | version = "0.52.6"
1837 | source = "registry+https://github.com/rust-lang/crates.io-index"
1838 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
1839 |
1840 | [[package]]
1841 | name = "windows_x86_64_gnullvm"
1842 | version = "0.42.2"
1843 | source = "registry+https://github.com/rust-lang/crates.io-index"
1844 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
1845 |
1846 | [[package]]
1847 | name = "windows_x86_64_gnullvm"
1848 | version = "0.48.5"
1849 | source = "registry+https://github.com/rust-lang/crates.io-index"
1850 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
1851 |
1852 | [[package]]
1853 | name = "windows_x86_64_gnullvm"
1854 | version = "0.52.6"
1855 | source = "registry+https://github.com/rust-lang/crates.io-index"
1856 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
1857 |
1858 | [[package]]
1859 | name = "windows_x86_64_msvc"
1860 | version = "0.42.2"
1861 | source = "registry+https://github.com/rust-lang/crates.io-index"
1862 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
1863 |
1864 | [[package]]
1865 | name = "windows_x86_64_msvc"
1866 | version = "0.48.5"
1867 | source = "registry+https://github.com/rust-lang/crates.io-index"
1868 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
1869 |
1870 | [[package]]
1871 | name = "windows_x86_64_msvc"
1872 | version = "0.52.6"
1873 | source = "registry+https://github.com/rust-lang/crates.io-index"
1874 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
1875 |
1876 | [[package]]
1877 | name = "winit"
1878 | version = "0.28.7"
1879 | source = "registry+https://github.com/rust-lang/crates.io-index"
1880 | checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94"
1881 | dependencies = [
1882 | "android-activity",
1883 | "bitflags 1.3.2",
1884 | "cfg_aliases",
1885 | "core-foundation",
1886 | "core-graphics",
1887 | "dispatch",
1888 | "instant",
1889 | "libc",
1890 | "log",
1891 | "mio",
1892 | "ndk",
1893 | "objc2",
1894 | "once_cell",
1895 | "orbclient",
1896 | "percent-encoding",
1897 | "raw-window-handle",
1898 | "redox_syscall 0.3.5",
1899 | "sctk-adwaita",
1900 | "smithay-client-toolkit",
1901 | "wasm-bindgen",
1902 | "wayland-client",
1903 | "wayland-commons",
1904 | "wayland-protocols",
1905 | "wayland-scanner",
1906 | "web-sys",
1907 | "windows-sys 0.45.0",
1908 | "x11-dl",
1909 | ]
1910 |
1911 | [[package]]
1912 | name = "winit_input_helper"
1913 | version = "0.14.1"
1914 | source = "registry+https://github.com/rust-lang/crates.io-index"
1915 | checksum = "5de0485e86aa2ee87d2d4c373a908c9548357bc65c5bce19fd884c8ea9eac4d7"
1916 | dependencies = [
1917 | "winit",
1918 | ]
1919 |
1920 | [[package]]
1921 | name = "winnow"
1922 | version = "0.5.40"
1923 | source = "registry+https://github.com/rust-lang/crates.io-index"
1924 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
1925 | dependencies = [
1926 | "memchr",
1927 | ]
1928 |
1929 | [[package]]
1930 | name = "x11-dl"
1931 | version = "2.21.0"
1932 | source = "registry+https://github.com/rust-lang/crates.io-index"
1933 | checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
1934 | dependencies = [
1935 | "libc",
1936 | "once_cell",
1937 | "pkg-config",
1938 | ]
1939 |
1940 | [[package]]
1941 | name = "xcursor"
1942 | version = "0.3.8"
1943 | source = "registry+https://github.com/rust-lang/crates.io-index"
1944 | checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61"
1945 |
1946 | [[package]]
1947 | name = "xml-rs"
1948 | version = "0.8.22"
1949 | source = "registry+https://github.com/rust-lang/crates.io-index"
1950 | checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26"
1951 |
1952 | [[package]]
1953 | name = "zerocopy"
1954 | version = "0.7.35"
1955 | source = "registry+https://github.com/rust-lang/crates.io-index"
1956 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
1957 | dependencies = [
1958 | "byteorder",
1959 | "zerocopy-derive",
1960 | ]
1961 |
1962 | [[package]]
1963 | name = "zerocopy-derive"
1964 | version = "0.7.35"
1965 | source = "registry+https://github.com/rust-lang/crates.io-index"
1966 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
1967 | dependencies = [
1968 | "proc-macro2",
1969 | "quote",
1970 | "syn 2.0.87",
1971 | ]
1972 |
--------------------------------------------------------------------------------