├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── trivial_cli_demo │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ └── main.rs │ └── thumbnail.png └── trivial_pixels_demo │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── src │ └── main.rs │ └── thumbnail.png └── src └── pixel_loop ├── canvas ├── crossterm.rs ├── in_memory.rs ├── mod.rs └── pixels.rs ├── color.rs ├── input ├── crossterm.rs ├── mod.rs └── pixels.rs ├── lib.rs └── tao.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎨 Pixel Loop 🔁 2 | 3 | [![Crate](https://img.shields.io/crates/v/pixel_loop.svg)](https://crates.io/crates/pixel_loop) 4 | [![Documentation](https://docs.rs/pixel_loop/badge.svg)](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/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | Thumbnail 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/trivial_cli_demo/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakobwesthoff/pixel_loop/6468d2793171673f06a605fe363f3ab9d6194d55/examples/trivial_cli_demo/thumbnail.png -------------------------------------------------------------------------------- /examples/trivial_pixels_demo/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | Thumbnail 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/trivial_pixels_demo/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakobwesthoff/pixel_loop/6468d2793171673f06a605fe363f3ab9d6194d55/examples/trivial_pixels_demo/thumbnail.png -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | --------------------------------------------------------------------------------