├── .editorconfig ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── README.tpl ├── build.rs ├── examples ├── show-image-tch.rs ├── show-image.rs └── show-raqote.rs ├── rustfmt.toml ├── shaders ├── Makefile ├── shader.vert ├── shader.vert.spv ├── uint8.frag ├── uint8.frag.spv ├── unorm8.frag └── unorm8.frag.spv ├── show-image-macros ├── Cargo.lock ├── Cargo.toml └── src │ └── lib.rs └── src ├── backend ├── context.rs ├── event.rs ├── mod.rs ├── mouse_cache.rs ├── proxy.rs ├── util │ ├── buffer.rs │ ├── gpu_image.rs │ ├── map_buffer.rs │ ├── mod.rs │ ├── retain_mut.rs │ └── uniforms_buffer.rs └── window.rs ├── background_thread.rs ├── error.rs ├── event ├── device.rs ├── mod.rs └── window.rs ├── features ├── image.rs ├── mod.rs ├── raqote.rs └── tch.rs ├── image_info.rs ├── image_types.rs ├── lib.rs ├── oneshot.rs ├── rectangle.rs └── termination.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: { branches: ["master", "main"] } 4 | pull_request: { branches: "*" } 5 | 6 | jobs: 7 | build_and_test: 8 | name: Build and test 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Install pytorch 12 | run: | 13 | pip3 install torch==2.6.0 --index-url https://download.pytorch.org/whl/cpu 14 | echo LIBTORCH_USE_PYTORCH=1 >> "$GITHUB_ENV" 15 | echo LD_LIBRARY_PATH="$(python -m site --user-site)/torch/lib${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH" >> "$GITHUB_ENV" 16 | - name: Checkout code 17 | uses: actions/checkout@master 18 | 19 | - uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.cargo/registry 23 | ~/.cargo/git 24 | target 25 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 26 | restore-keys: ${{ runner.os }}-cargo 27 | 28 | - name: Build 29 | uses: actions-rs/cargo@v1 30 | with: 31 | command: build 32 | args: --release --features full --color=always 33 | 34 | - name: Clippy 35 | uses: actions-rs/clippy-check@v1 36 | with: 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | args: --release --features full 39 | 40 | - name: Test 41 | uses: actions-rs/cargo@v1 42 | with: 43 | command: test 44 | args: --release --features full --color=always 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.14.1 - 2025-02-23 2 | * Add `WindowHandle::set_title()` to update the title of a window. 3 | * Bump maximum supported version of `tch` to `v0.19.x`. 4 | * Bump maximum supported version of `glam` to `v0.30.x`. 5 | 6 | # v0.14.0 - 2024-03-30 7 | * Update to `winit` version `0.28`. 8 | * Update to `wgpu` version `0.17`. 9 | * Expose `winit` support for `WindowTouchpadMagnifyEvent` and `WindowTouchpadRotateEvent` on supported platforms. 10 | * Bump minimum `tch` version to `0.13`. 11 | * Bump maximum `tch` version to `0.15`. 12 | * Bump supported `image` version to `0.25`. 13 | * Preserve order of overlays when removing one. 14 | 15 | # v0.13.1 - 2022-09-16 16 | * Tweak the behavior of `set_overlay` to preserve visibility of existing overlays. 17 | * Add `window.is_overlay_visible()`. 18 | 19 | # v0.13.0 - 2022-09-15 20 | * Autoselect compatible present mode for all platforms. 21 | * Change `window.set_outer_position()` to take an `impl Into`. 22 | * Change `window.set_inner_size()` to take an `impl Into`. 23 | * Redesign overlay API. 24 | * Bump maximum `tch` version to `0.8.x`. 25 | * Use `glam` version `0.20` or `0.21`. 26 | * Update internal dependencies. 27 | 28 | # v0.12.3 - 2022-05-22 29 | * Add `WindowHandle::set_outer_position`. 30 | 31 | # v0.12.2 - 2022-05-22 32 | * Add fullscreen mode for windows. 33 | * Fix behavior when a window event handler destroys its window. 34 | 35 | # v0.12.1 - 2022-04-15 36 | * Change `Container` for `image` crate support to accept any `Deref", 15 | "Hans Gaiser ", 16 | "RoboHouse ", 17 | ] 18 | 19 | keywords = ["image", "visualize", "show", "debug"] 20 | categories = [ 21 | "development-tools::debugging", 22 | "multimedia::images", 23 | "visualization", 24 | "gui", 25 | ] 26 | 27 | [[example]] 28 | name = "show-raqote" 29 | required-features = ["raqote"] 30 | 31 | [[example]] 32 | name = "show-image-tch" 33 | required-features = ["tch"] 34 | 35 | [features] 36 | default = ["macros"] 37 | doc-only = ["tch/doc-only"] 38 | full = ["save", "image", "tch", "raqote", "macros"] 39 | macros = ["show-image-macros"] 40 | nightly = [] 41 | save = ["tinyfiledialogs", "png", "log"] 42 | 43 | [dependencies] 44 | futures = { version = "0.3.28", default-features = false, features = ["executor"] } 45 | glam = ">=0.20.0, <0.31.0" 46 | image = { version = "0.25.0", optional = true, default-features = false } 47 | indexmap = "2.0.0" 48 | log = { version = "0.4.19", optional = true } 49 | png = { version = "0.17.9", optional = true } 50 | raqote = { version = "0.8.2", optional = true, default-features = false } 51 | show-image-macros = { version = "=0.12.4", optional = true, path = "show-image-macros" } 52 | tch = { version = ">=0.13.0, <0.20.0", optional = true } 53 | tinyfiledialogs = { version = "3.9.1", optional = true } 54 | wgpu = { version = "0.17.0", features = ["spirv"] } 55 | winit = "0.28.6" 56 | 57 | [dev-dependencies] 58 | show-image = { path = ".", features = ["image"] } 59 | assert2 = "0.3.11" 60 | image = { version = "0.25.0", default-features = true } 61 | env_logger = "0.11.6" 62 | 63 | [build-dependencies] 64 | rustc_version = "0.4.0" 65 | 66 | [package.metadata.docs.rs] 67 | features = ["doc-only", "full", "nightly"] 68 | 69 | [workspace] 70 | members = ["show-image-macros"] 71 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019-2020, RoboHouse 4 | Copyright (c) 2019-2020, Maarten de Vries 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Docs.rs](https://docs.rs/show-image/badge.svg)](https://docs.rs/crate/show-image/) 2 | [![CI](https://github.com/robohouse-delft/show-image-rs/workflows/CI/badge.svg)](https://github.com/robohouse-delft/show-image-rs/actions?query=workflow%3ACI+branch%3Amain) 3 | 4 | # show-image 5 | 6 | `show-image` is a library for quickly displaying images. 7 | It is intended as a debugging aid for writing image processing code. 8 | The library is not intended for making full-featured GUIs, 9 | but you can process keyboard events from the created windows. 10 | 11 | ## Supported image types. 12 | The library aims to support as many different data types used to represent images. 13 | To keep the dependency graph as small as possible, 14 | support for third party libraries must be enabled explicitly with feature flags. 15 | 16 | Currently, the following types are supported: 17 | * The [`Image`] and [`ImageView`] types from this crate. 18 | * [`image::DynamicImage`][::image::DynamicImage] and [`image::ImageBuffer`][::image::ImageBuffer] (requires the `"image"` feature). 19 | * [`tch::Tensor`][::tch::Tensor] (requires the `"tch"` feature). 20 | * [`raqote::DrawTarget`][::raqote::DrawTarget] and [`raqote::Image`][::raqote::Image] (requires the `"raqote"` feature). 21 | 22 | If you think support for a some data type is missing, 23 | feel free to send a PR or create an issue on GitHub. 24 | 25 | ## Global context and threading 26 | The library uses a global context that runs an event loop. 27 | This context must be initialized before any `show-image` functions can be used. 28 | Additionally, some platforms require the event loop to be run in the main thread. 29 | To ensure portability, the same restriction is enforced on all platforms. 30 | 31 | The easiest way to initialize the global context and run the event loop in the main thread 32 | is to use the [`main`] attribute macro on your main function. 33 | If you want to run some code in the main thread before the global context takes over, 34 | you can use the [`run_context()`] function or one of it's variations instead. 35 | Note that you must still call those functions from the main thread, 36 | and they do not return control back to the caller. 37 | 38 | ## Event handling. 39 | You can register an event handler to run in the global context thread using [`WindowProxy::add_event_handler()`] or some of the similar functions. 40 | You can also register an event handler directly with the context to handle global events (including all window events). 41 | Since these event handlers run in the event loop, they should not block for any significant time. 42 | 43 | You can also receive events using [`WindowProxy::event_channel()`] or [`ContextProxy::event_channel()`]. 44 | These functions create a new channel for receiving window events or global events, respectively. 45 | As long as you're receiving the events in your own thread, you can block as long as you like. 46 | 47 | ## Saving displayed images. 48 | If the `save` feature is enabled, windows allow the displayed image to be saved using `Ctrl+S` or `Ctrl+Shift+S`. 49 | The first shortcut will open a file dialog to save the currently displayed image. 50 | The second shortcut will directly save the image in the current working directory using the name of the image. 51 | 52 | The image is saved without any overlays. 53 | To save an image including overlays, add `Alt` to the shortcut: `Ctrl+Alt+S` and `Ctrl+Alt+Shift+S`. 54 | 55 | Note that images are saved in a background thread. 56 | To ensure that no data loss occurs, call [`exit()`] to terminate the process rather than [`std::process::exit()`]. 57 | That will ensure that the background threads are joined before the process is terminated. 58 | 59 | ## Example 1: Showing an image. 60 | ```rust 61 | use show_image::{ImageView, ImageInfo, create_window}; 62 | 63 | #[show_image::main] 64 | fn main() -> Result<(), Box> { 65 | 66 | let image = ImageView::new(ImageInfo::rgb8(1920, 1080), pixel_data); 67 | 68 | // Create a window with default options and display the image. 69 | let window = create_window("image", Default::default())?; 70 | window.set_image("image-001", image)?; 71 | 72 | Ok(()) 73 | } 74 | ``` 75 | 76 | ## Example 2: Handling keyboard events using an event channel. 77 | ```rust 78 | use show_image::{event, create_window}; 79 | 80 | // Create a window and display the image. 81 | let window = create_window("image", Default::default())?; 82 | window.set_image("image-001", &image)?; 83 | 84 | // Print keyboard events until Escape is pressed, then exit. 85 | // If the user closes the window, the channel is closed and the loop also exits. 86 | for event in window.event_channel()? { 87 | if let event::WindowEvent::KeyboardInput(event) = event { 88 | println!("{:#?}", event); 89 | if event.input.key_code == Some(event::VirtualKeyCode::Escape) && event.input.state.is_pressed() { 90 | break; 91 | } 92 | } 93 | } 94 | 95 | ``` 96 | 97 | ## Back-end and GPU selection 98 | 99 | This crate uses [`wgpu`] for rendering. 100 | You can force the selection of a specfic WGPU backend by setting the `WGPU_BACKEND` environment variable to one of the supported values: 101 | 102 | * `primary`: Use the primary backend for the platform (the default). 103 | * `vulkan`: Use the vulkan back-end. 104 | * `metal`: Use the metal back-end. 105 | * `dx12`: Use the DirectX 12 back-end. 106 | * `dx11`: Use the DirectX 11 back-end. 107 | * `gl`: Use the OpenGL back-end. 108 | * `webgpu`: Use the browser WebGPU back-end. 109 | 110 | You can also influence the GPU selection by setting the `WGPU_POWER_PREF` environment variable: 111 | 112 | * `low`: Prefer a low power GPU (the default). 113 | * `high`: Prefer a high performance GPU. 114 | 115 | [`Image`]: https://docs.rs/show-image/latest/show_image/enum.Image.html 116 | [`ImageView`]: https://docs.rs/show-image/latest/show_image/struct.ImageView.html 117 | [::image::DynamicImage]: https://docs.rs/image/latest/image/dynimage/enum.DynamicImage.html 118 | [::image::ImageBuffer]: https://docs.rs/image/latest/image/buffer_/struct.ImageBuffer.html 119 | [::tch::Tensor]: https://docs.rs/tch/latest/tch/wrappers/tensor/struct.Tensor.html 120 | [::raqote::DrawTarget]: https://docs.rs/raqote/latest/raqote/struct.DrawTarget.html 121 | [::raqote::Image]: https://docs.rs/raqote/latest/raqote/struct.Image.html 122 | [`main`]: https://docs.rs/show-image/latest/show_image/attr.main.html 123 | [`run_context()`]: https://docs.rs/show-image/latest/show_image/fn.run_context.html 124 | [`WindowProxy::add_event_handler()`]: https://docs.rs/show-image/latest/show_image/struct.WindowProxy.html#method.add_event_handler 125 | [`WindowProxy::event_channel()`]: https://docs.rs/show-image/latest/show_image/struct.WindowProxy.html#method.event_channel 126 | [`ContextProxy::event_channel()`]: https://docs.rs/show-image/latest/show_image/struct.ContextProxy.html#method.event_channel 127 | [`exit()`]: https://docs.rs/show-image/latest/show_image/fn.exit.html 128 | [`std::process::exit()`]: https://doc.rust-lang.org/nightly/std/process/fn.exit.html 129 | [`wgpu`]: https://docs.rs/wgpu 130 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | [![Docs.rs](https://docs.rs/show-image/badge.svg)](https://docs.rs/crate/{{crate}}/) 2 | [![CI](https://github.com/robohouse-delft/show-image-rs/workflows/CI/badge.svg)](https://github.com/robohouse-delft/show-image-rs/actions?query=workflow%3ACI+branch%3Amain) 3 | 4 | # {{crate}} 5 | 6 | {{readme}} 7 | 8 | [`Image`]: https://docs.rs/show-image/latest/show_image/enum.Image.html 9 | [`ImageView`]: https://docs.rs/show-image/latest/show_image/struct.ImageView.html 10 | [::image::DynamicImage]: https://docs.rs/image/latest/image/dynimage/enum.DynamicImage.html 11 | [::image::ImageBuffer]: https://docs.rs/image/latest/image/buffer_/struct.ImageBuffer.html 12 | [::tch::Tensor]: https://docs.rs/tch/latest/tch/wrappers/tensor/struct.Tensor.html 13 | [::raqote::DrawTarget]: https://docs.rs/raqote/latest/raqote/struct.DrawTarget.html 14 | [::raqote::Image]: https://docs.rs/raqote/latest/raqote/struct.Image.html 15 | [`main`]: https://docs.rs/show-image/latest/show_image/attr.main.html 16 | [`run_context()`]: https://docs.rs/show-image/latest/show_image/fn.run_context.html 17 | [`WindowProxy::add_event_handler()`]: https://docs.rs/show-image/latest/show_image/struct.WindowProxy.html#method.add_event_handler 18 | [`WindowProxy::event_channel()`]: https://docs.rs/show-image/latest/show_image/struct.WindowProxy.html#method.event_channel 19 | [`ContextProxy::event_channel()`]: https://docs.rs/show-image/latest/show_image/struct.ContextProxy.html#method.event_channel 20 | [`exit()`]: https://docs.rs/show-image/latest/show_image/fn.exit.html 21 | [`std::process::exit()`]: https://doc.rust-lang.org/nightly/std/process/fn.exit.html 22 | [`wgpu`]: https://docs.rs/wgpu 23 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use rustc_version::{version_meta, Channel}; 2 | 3 | fn main() { 4 | println!("cargo:rerun-if-changed=build.rs"); 5 | if version_meta().unwrap().channel <= Channel::Nightly { 6 | println!("cargo:rustc-cfg=nightly"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/show-image-tch.rs: -------------------------------------------------------------------------------- 1 | use show_image::event; 2 | use show_image::tch::TensorAsImage; 3 | 4 | #[show_image::main] 5 | fn main() -> Result<(), String> { 6 | env_logger::init(); 7 | 8 | let args: Vec<_> = std::env::args().collect(); 9 | if args.len() != 2 { 10 | return Err(format!("usage: {} IMAGE", args[0])); 11 | } 12 | 13 | let path = std::path::Path::new(&args[1]); 14 | let name = path.file_stem().and_then(|x| x.to_str()).unwrap_or("image"); 15 | 16 | let tensor = tch::vision::imagenet::load_image(path).map_err(|e| format!("failed to load image from {:?}: {}", path, e))?; 17 | let tensor = tch::vision::imagenet::unnormalize(&tensor).unwrap(); 18 | let image: show_image::Image = tensor.as_image_guess_rgb().into(); 19 | 20 | let image_info = show_image::image_info(&image).map_err(|e| e.to_string())?; 21 | println!("{:#?}", image_info); 22 | 23 | let window = show_image::create_window("image", Default::default()).map_err(|e| e.to_string())?; 24 | 25 | window.set_image(name, image).map_err(|e| e.to_string())?; 26 | 27 | // Wait for the window to be closed or Escape to be pressed. 28 | for event in window.event_channel().map_err(|e| e.to_string())? { 29 | if let event::WindowEvent::KeyboardInput(event) = event { 30 | if !event.is_synthetic && event.input.key_code == Some(event::VirtualKeyCode::Escape) && event.input.state.is_pressed() { 31 | println!("Escape pressed!"); 32 | break; 33 | } 34 | } 35 | } 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/show-image.rs: -------------------------------------------------------------------------------- 1 | use show_image::event; 2 | 3 | #[show_image::main] 4 | fn main() -> Result<(), String> { 5 | env_logger::init(); 6 | 7 | let args: Vec<_> = std::env::args().collect(); 8 | if args.len() != 2 { 9 | return Err(format!("usage: {} IMAGE", args[0])); 10 | } 11 | 12 | let path = std::path::Path::new(&args[1]); 13 | let name = path.file_stem().and_then(|x| x.to_str()).unwrap_or("image"); 14 | 15 | let image = image::open(path).map_err(|e| format!("Failed to read image from {:?}: {}", path, e))?; 16 | 17 | let image_info = show_image::image_info(&image).map_err(|e| e.to_string())?; 18 | println!("{:#?}", image_info); 19 | 20 | let window = show_image::create_window("image", Default::default()).map_err(|e| e.to_string())?; 21 | window.set_image(name, image).map_err(|e| e.to_string())?; 22 | 23 | // Wait for the window to be closed or Escape to be pressed. 24 | for event in window.event_channel().map_err(|e| e.to_string())? { 25 | if let event::WindowEvent::KeyboardInput(event) = event { 26 | if !event.is_synthetic && event.input.key_code == Some(event::VirtualKeyCode::Escape) && event.input.state.is_pressed() { 27 | println!("Escape pressed!"); 28 | break; 29 | } 30 | } 31 | } 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /examples/show-raqote.rs: -------------------------------------------------------------------------------- 1 | use raqote::DrawOptions; 2 | use raqote::DrawTarget; 3 | use raqote::PathBuilder; 4 | use raqote::StrokeStyle; 5 | use show_image::event::ModifiersState; 6 | use show_image::event::VirtualKeyCode; 7 | use show_image::event::WindowEvent; 8 | use show_image::Image; 9 | 10 | #[show_image::main] 11 | fn main() -> Result<(), String> { 12 | env_logger::init(); 13 | 14 | let args: Vec<_> = std::env::args().collect(); 15 | if args.len() != 1 { 16 | return Err(format!("usage: {}", args[0])); 17 | } 18 | 19 | let mut image = DrawTarget::new(1000, 1000); 20 | let mut overlay = DrawTarget::new(500, 1000); 21 | image.set_transform(&raqote::Transform::scale(1000.0, 1000.0)); 22 | overlay.set_transform(&raqote::Transform::scale(1000.0, 1000.0)); 23 | 24 | let black = raqote::Color::new(255, 0, 0, 0).into(); 25 | let white = raqote::Color::new(255, 255, 255, 255).into(); 26 | let red = raqote::Color::new(255, 190, 0, 0).into(); 27 | let yellow = raqote::Color::new(255, 255, 215, 85).into(); 28 | let blue = raqote::Color::new(255, 0, 50, 160).into(); 29 | 30 | let draw_options = DrawOptions::new(); 31 | 32 | image.fill_rect(0.0, 0.0, 1.0, 1.0, &white, &draw_options); 33 | 34 | image.fill_rect(0.00, 0.00, 0.25, 0.30, &red, &draw_options); 35 | image.fill_rect(0.00, 0.70, 0.25, 0.30, &blue, &draw_options); 36 | image.fill_rect(0.85, 0.70, 0.15, 0.30, &yellow, &draw_options); 37 | 38 | let mut path = PathBuilder::new(); 39 | path.move_to(0.25, 0.00); 40 | path.line_to(0.25, 1.00); 41 | image.stroke( 42 | &path.finish(), 43 | &black, 44 | &StrokeStyle { 45 | width: 0.03, 46 | ..Default::default() 47 | }, 48 | &draw_options, 49 | ); 50 | 51 | let mut path = PathBuilder::new(); 52 | path.move_to(0.00, 0.30); 53 | path.line_to(0.25, 0.30); 54 | image.stroke( 55 | &path.finish(), 56 | &black, 57 | &StrokeStyle { 58 | width: 0.04, 59 | ..Default::default() 60 | }, 61 | &draw_options, 62 | ); 63 | 64 | let mut path = PathBuilder::new(); 65 | path.move_to(0.00, 0.70); 66 | path.line_to(1.00, 0.70); 67 | image.stroke( 68 | &path.finish(), 69 | &black, 70 | &StrokeStyle { 71 | width: 0.03, 72 | ..Default::default() 73 | }, 74 | &draw_options, 75 | ); 76 | 77 | let mut path = PathBuilder::new(); 78 | path.move_to(0.85, 0.70); 79 | path.line_to(0.85, 1.00); 80 | image.stroke( 81 | &path.finish(), 82 | &black, 83 | &StrokeStyle { 84 | width: 0.03, 85 | ..Default::default() 86 | }, 87 | &draw_options, 88 | ); 89 | 90 | let mut path = PathBuilder::new(); 91 | path.move_to(0.85, 0.70); 92 | path.line_to(0.85, 1.00); 93 | image.stroke( 94 | &path.finish(), 95 | &black, 96 | &StrokeStyle { 97 | width: 0.03, 98 | ..Default::default() 99 | }, 100 | &draw_options, 101 | ); 102 | 103 | let mut path = PathBuilder::new(); 104 | path.move_to(0.00, 0.00); 105 | path.line_to(1.00, 1.00); 106 | overlay.stroke( 107 | &path.finish(), 108 | &yellow, 109 | &StrokeStyle { 110 | width: 0.03, 111 | ..Default::default() 112 | }, 113 | &draw_options, 114 | ); 115 | 116 | let image: Image = image.into(); 117 | let image_view = image.as_image_view().map_err(|x| x.to_string())?; 118 | println!("{:#?}", image_view.info()); 119 | let overlay: show_image::Image = overlay.into(); 120 | 121 | let window = show_image::context().run_function_wait(move |context| -> Result<_, String> { 122 | let mut window = context.create_window("image", Default::default()).map_err(|e| e.to_string())?; 123 | window.set_image("mondriaan", &image.as_image_view().map_err(|e| e.to_string())?); 124 | window.set_overlay("overlay", &overlay.as_image_view().map_err(|e| e.to_string())?, false); 125 | Ok(window.proxy()) 126 | })?; 127 | 128 | 129 | // Wait for the window to be closed or Escape to be pressed. 130 | for event in window.event_channel().map_err(|e| e.to_string())? { 131 | if let WindowEvent::KeyboardInput(event) = event { 132 | if event.is_synthetic || !event.input.state.is_pressed() { 133 | continue; 134 | } 135 | if event.input.key_code == Some(VirtualKeyCode::Escape) { 136 | println!("Escape pressed!"); 137 | break; 138 | } else if event.input.key_code == Some(VirtualKeyCode::O) && event.input.modifiers == ModifiersState::CTRL { 139 | println!("Ctrl+O pressed, toggling overlay"); 140 | window.run_function_wait(|mut window| { 141 | window.toggle_overlay_visible("overlay").unwrap(); 142 | }).map_err(|e| e.to_string())?; 143 | } 144 | } 145 | } 146 | 147 | Ok(()) 148 | } 149 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | tab_spaces = 4 3 | max_width = 140 4 | imports_layout = "HorizontalVertical" 5 | match_block_trailing_comma = true 6 | overflow_delimited_expr = true 7 | reorder_impl_items = true 8 | unstable_features = true 9 | use_field_init_shorthand = true 10 | -------------------------------------------------------------------------------- /shaders/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | 3 | all: shader.vert.spv uint8.frag.spv unorm8.frag.spv 4 | 5 | %.spv: % 6 | glslangValidator -V -o "$@" "$<" 7 | -------------------------------------------------------------------------------- /shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 420 2 | // vi: ft=glsl 3 | 4 | out gl_PerVertex { 5 | vec4 gl_Position; 6 | }; 7 | 8 | layout(location = 0) out vec2 texture_coords; 9 | 10 | layout(set = 0, binding = 0) uniform WindowUniforms { 11 | vec2 image_size; 12 | mat3 transform; 13 | }; 14 | 15 | const vec2 POSITIONS[6] = vec2[6]( 16 | vec2(0.0, 0.0), 17 | vec2(1.0, 0.0), 18 | vec2(1.0, 1.0), 19 | vec2(0.0, 0.0), 20 | vec2(1.0, 1.0), 21 | vec2(0.0, 1.0) 22 | ); 23 | 24 | // Flip screen space coordinates to put the origin at the top left corner, 25 | // and have the positive Y axis pointing down. 26 | const mat3 flip_y = mat3(vec3(1.0, 0.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 1.0, 1.0)); 27 | 28 | void main() { 29 | vec2 position = (flip_y * transform * vec3(POSITIONS[gl_VertexIndex], 1.0)).xy; 30 | 31 | // Adjust for weird screen space going from -1.0 to 1.0 instead of 0.0 to 1.0. 32 | position = 2.0 * position - vec2(1.0, 1.0); 33 | 34 | gl_Position = vec4(position, 0.0, 1.0); 35 | texture_coords = (image_size) * POSITIONS[gl_VertexIndex]; 36 | } 37 | -------------------------------------------------------------------------------- /shaders/shader.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robohouse-delft/show-image-rs/22d4740cfd3848db49ab7253c073876674a68bbd/shaders/shader.vert.spv -------------------------------------------------------------------------------- /shaders/uint8.frag: -------------------------------------------------------------------------------- 1 | #version 430 2 | // vi: ft=glsl 3 | 4 | layout(location = 0) in vec2 texture_coords; 5 | layout(location = 0) out uvec4 out_color; 6 | 7 | layout(set = 1, binding = 0) uniform InfoBlock { 8 | uint format; 9 | uint width; 10 | uint height; 11 | uint stride_x; 12 | uint stride_y; 13 | }; 14 | 15 | layout(set = 1, binding = 1) buffer readonly Data { 16 | uint data[]; 17 | }; 18 | 19 | uint extract_u8(uint i) { 20 | uint word = data[i / 4]; 21 | uint offset = (i % 4) * 8; 22 | return word >> offset & 0xFF; 23 | } 24 | 25 | uvec4 get_pixel(uint x, uint y) { 26 | uint i = x * stride_x + y * stride_y; 27 | 28 | // Mono8 29 | if (format == 0) { 30 | float mono = extract_u8(i); 31 | return uvec4(mono, mono, mono, 255); 32 | 33 | // MonoAlpha8(Unpremultiplied) 34 | } else if (format == 1) { 35 | uint mono = extract_u8(i); 36 | uint a = extract_u8(i + 1); 37 | return uvec4(mono, mono, mono, a); 38 | 39 | // MonoAlpha8(Premultiplied) 40 | } else if (format == 2) { 41 | uint a = extract_u8(i + 1); 42 | uint mono = extract_u8(i) * 255 / a; 43 | return uvec4(mono, mono, mono, a); 44 | 45 | // Bgr8 46 | } else if (format == 3) { 47 | uint b = extract_u8(i + 0); 48 | uint g = extract_u8(i + 1); 49 | uint r = extract_u8(i + 2); 50 | return uvec4(r, g, b, 255); 51 | 52 | // Bgra8(Unpremultiplied) 53 | } else if (format == 4) { 54 | uint b = extract_u8(i + 0); 55 | uint g = extract_u8(i + 1); 56 | uint r = extract_u8(i + 2); 57 | uint a = extract_u8(i + 3); 58 | return uvec4(r, g, b, a); 59 | 60 | // Bgra8(Premultiplied) 61 | } else if (format == 5) { 62 | uint a = extract_u8(i + 3); 63 | uint b = extract_u8(i + 0) * 255 / a; 64 | uint g = extract_u8(i + 1) * 255 / a; 65 | uint r = extract_u8(i + 2) * 255 / a; 66 | return uvec4(r, g, b, a); 67 | 68 | // Rgb8 69 | } else if (format == 6) { 70 | uint r = extract_u8(i + 0); 71 | uint g = extract_u8(i + 1); 72 | uint b = extract_u8(i + 2); 73 | return uvec4(r, g, b, 255); 74 | 75 | // Rgba8(Unpremultiplied) 76 | } else if (format == 7) { 77 | uint r = extract_u8(i + 0); 78 | uint g = extract_u8(i + 1); 79 | uint b = extract_u8(i + 2); 80 | uint a = extract_u8(i + 3); 81 | return uvec4(r, g, b, a); 82 | 83 | // Rgba8(Premultiplied) 84 | } else if (format == 8) { 85 | uint a = extract_u8(i + 3); 86 | uint r = extract_u8(i + 0) * 255 / a; 87 | uint g = extract_u8(i + 1) * 255 / a; 88 | uint b = extract_u8(i + 2) * 255 / a; 89 | return uvec4(r, g, b, a); 90 | 91 | } else { 92 | return uvec4(255, 0, 255, 255); 93 | } 94 | } 95 | 96 | void main() { 97 | uint x = uint(floor(texture_coords.x)); 98 | uint y = uint(floor(texture_coords.y)); 99 | if (x >= width || y >= height) { 100 | out_color = uvec4(0, 0, 0, 0); 101 | } else { 102 | out_color = get_pixel(x, y); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /shaders/uint8.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robohouse-delft/show-image-rs/22d4740cfd3848db49ab7253c073876674a68bbd/shaders/uint8.frag.spv -------------------------------------------------------------------------------- /shaders/unorm8.frag: -------------------------------------------------------------------------------- 1 | #version 430 2 | // vi: ft=glsl 3 | 4 | layout(location = 0) in vec2 texture_coords; 5 | layout(location = 0) out vec4 out_color; 6 | 7 | layout(set = 1, binding = 0) uniform InfoBlock { 8 | uint format; 9 | uint width; 10 | uint height; 11 | uint stride_x; 12 | uint stride_y; 13 | }; 14 | 15 | layout(set = 1, binding = 1) buffer readonly Data { 16 | uint data[]; 17 | }; 18 | 19 | uint extract_u8(uint i) { 20 | uint word = data[i / 4]; 21 | uint offset = (i % 4) * 8; 22 | return word >> offset & 0xFF; 23 | } 24 | 25 | float extract_unorm8(uint i) { 26 | return float(extract_u8(i)) / 255.0; 27 | } 28 | 29 | vec4 get_pixel(uint x, uint y) { 30 | uint i = x * stride_x + y * stride_y; 31 | 32 | // Mono8 33 | if (format == 0) { 34 | float mono = extract_unorm8(i); 35 | return vec4(mono, mono, mono, 1.0); 36 | 37 | // MonoAlpha8(Unpremultiplied) 38 | } else if (format == 1) { 39 | float mono = extract_unorm8(i); 40 | float a = extract_unorm8(i + 1); 41 | return vec4(mono, mono, mono, a); 42 | 43 | // MonoAlpha8(Premultiplied) 44 | } else if (format == 2) { 45 | float a = float(extract_u8(i + 1)); 46 | float mono = float(extract_u8(i)) / a; 47 | return vec4(mono, mono, mono, a); 48 | 49 | // Bgr8 50 | } else if (format == 3) { 51 | float b = extract_unorm8(i + 0); 52 | float g = extract_unorm8(i + 1); 53 | float r = extract_unorm8(i + 2); 54 | return vec4(r, g, b, 1.0); 55 | 56 | // Bgra8(Unpremultiplied) 57 | } else if (format == 4) { 58 | float b = extract_unorm8(i + 0); 59 | float g = extract_unorm8(i + 1); 60 | float r = extract_unorm8(i + 2); 61 | float a = extract_unorm8(i + 3); 62 | return vec4(r, g, b, a); 63 | 64 | // Bgra8(Premultiplied) 65 | } else if (format == 5) { 66 | float a = float(extract_u8(i + 3)); 67 | float b = float(extract_u8(i + 0)) / a; 68 | float g = float(extract_u8(i + 1)) / a; 69 | float r = float(extract_u8(i + 2)) / a; 70 | return vec4(r, g, b, a / 255.0); 71 | 72 | // Rgb8 73 | } else if (format == 6) { 74 | float r = extract_unorm8(i + 0); 75 | float g = extract_unorm8(i + 1); 76 | float b = extract_unorm8(i + 2); 77 | return vec4(r, g, b, 1.0); 78 | 79 | // Rgba8(Unpremultiplied) 80 | } else if (format == 7) { 81 | float r = extract_unorm8(i + 0); 82 | float g = extract_unorm8(i + 1); 83 | float b = extract_unorm8(i + 2); 84 | float a = extract_unorm8(i + 3); 85 | return vec4(r, g, b, a); 86 | 87 | // Rgba8(Premultiplied) 88 | } else if (format == 8) { 89 | float a = float(extract_u8(i + 3)); 90 | float r = float(extract_u8(i + 0)) / a; 91 | float g = float(extract_u8(i + 1)) / a; 92 | float b = float(extract_u8(i + 2)) / a; 93 | return vec4(r, g, b, a / 255.0); 94 | 95 | } else { 96 | return vec4(1.0, 0.0, 1.0, 1.0); 97 | } 98 | } 99 | 100 | void main() { 101 | uint x = uint(floor(texture_coords.x)); 102 | uint y = uint(floor(texture_coords.y)); 103 | if (x >= width || y >= height) { 104 | out_color = vec4(0.0, 0.0, 0.0, 0.0); 105 | } else { 106 | out_color = get_pixel(x, y); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /shaders/unorm8.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robohouse-delft/show-image-rs/22d4740cfd3848db49ab7253c073876674a68bbd/shaders/unorm8.frag.spv -------------------------------------------------------------------------------- /show-image-macros/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "show-image-macros" 5 | version = "0.1.0" 6 | -------------------------------------------------------------------------------- /show-image-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "show-image-macros" 4 | version = "0.12.4" 5 | license = "BSD-2-Clause" 6 | description = "macros for the show-image crate" 7 | edition = "2018" 8 | 9 | repository = "https://github.com/robohouse-delft/show-image-rs" 10 | 11 | authors = ["Maarten de Vries "] 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | quote = "1.0.32" 18 | proc-macro2 = "1.0.66" 19 | syn = { version = "1.0.109", features = ["full"] } 20 | 21 | [dev-dependencies] 22 | show-image = { path = ".." } 23 | -------------------------------------------------------------------------------- /show-image-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains helper macros for the `show-image` crate. 2 | //! You should not depend on this crate directly. 3 | //! Instead, enable the `macro` feature of the `show-image` crate and use them from there. 4 | 5 | /// Wrap your program entry point for correct initialization of the `show-image` global context. 6 | /// 7 | /// The `show-image` global context will run in the main thread, 8 | /// and your own entry point will be executed in a new thread. 9 | /// When your entry point returns, the whole process will exit. 10 | /// 11 | /// Any background tasks spawned by `show-image` will be joined before the process terminates, 12 | /// but any threads spawned by user code will be killed. 13 | /// You should manually join those if needed. 14 | /// 15 | /// Note that we are very sorry about stealing your main thread. 16 | /// We would rather let you keep the main thread and run the global context in a background thread. 17 | /// However, some platforms require all GUI code to run in the "main" thread (looking at you, OS X). 18 | /// To ensure portability, the same restriction is enforced on other platforms. 19 | /// 20 | /// # Examples 21 | /// 22 | /// ```no_run 23 | /// use show_image::{ContextProxy, WindowOptions}; 24 | /// # use std::error::Error; 25 | /// # mod image { 26 | /// # pub fn open(_path: impl AsRef) -> Result, std::io::Error> { 27 | /// # Ok(show_image::ImageView::new(show_image::ImageInfo::rgb8(1, 1), &[255, 0, 0])) 28 | /// # } 29 | /// # } 30 | /// 31 | /// #[show_image::main] 32 | /// fn main() -> Result<(), Box> { 33 | /// let window = show_image::create_window("My Awesome Window", WindowOptions::default())?; 34 | /// let image = image::open("/path/to/image.png")?; 35 | /// 36 | /// window.set_image("image", image)?; 37 | /// window.wait_until_destroyed()?; 38 | /// Ok(()) 39 | /// } 40 | /// ``` 41 | #[proc_macro_attribute] 42 | pub fn main(attribs: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream { 43 | match details::main(attribs.into(), input.into()) { 44 | Ok(x) => x.into(), 45 | Err(e) => details::error_to_tokens(e).into(), 46 | } 47 | } 48 | 49 | mod details { 50 | use quote::quote; 51 | 52 | /// Convert a syn::Error into a compile error with a dummy main. 53 | /// 54 | /// The dummy main prevents the compiler from complaining about a missing entry point, which is confusing noise. 55 | /// We only want the compiler to show the real error. 56 | pub fn error_to_tokens(error: syn::Error) -> proc_macro2::TokenStream { 57 | let error = error.to_compile_error(); 58 | quote! { 59 | #error 60 | fn main() { 61 | panic!("#[show_image::main]: compilation should have failed, please report this bug"); 62 | } 63 | } 64 | } 65 | 66 | pub fn main(arguments: proc_macro2::TokenStream, input: proc_macro2::TokenStream) -> syn::Result { 67 | if !arguments.is_empty() { 68 | return Err(syn::Error::new_spanned(arguments, "unexpected macro arguments")); 69 | } 70 | 71 | let function: syn::ItemFn = syn::parse2(input)?; 72 | let name = function.sig.ident.clone(); 73 | let visibility = &function.vis; 74 | 75 | Ok(quote! { 76 | #visibility fn main() { 77 | #function 78 | ::show_image::run_context(#name); 79 | } 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/backend/event.rs: -------------------------------------------------------------------------------- 1 | use super::mouse_cache::MouseCache; 2 | 3 | pub fn convert_winit_event( 4 | event: winit::event::Event<()>, 5 | mouse_cache: &MouseCache, 6 | ) -> Option { 7 | use crate::event::Event as C; 8 | use winit::event::Event as W; 9 | 10 | match event { 11 | W::UserEvent(_) => None, 12 | W::WindowEvent { window_id, event } => Some(convert_winit_window_event(window_id, event, mouse_cache)?.into()), 13 | W::DeviceEvent { device_id, event } => Some(convert_winit_device_event(device_id, event).into()), 14 | W::NewEvents(_) => Some(C::NewEvents), 15 | W::MainEventsCleared => Some(C::MainEventsCleared), 16 | W::RedrawRequested(window_id) => Some(C::WindowEvent(crate::event::WindowRedrawRequestedEvent { window_id }.into())), 17 | W::RedrawEventsCleared => Some(C::RedrawEventsCleared), 18 | // You can't stop the event loop! 19 | W::LoopDestroyed => None, 20 | W::Suspended => Some(C::Suspended), 21 | W::Resumed => Some(C::Resumed), 22 | } 23 | } 24 | 25 | pub fn convert_winit_device_event( 26 | device_id: winit::event::DeviceId, 27 | event: winit::event::DeviceEvent, 28 | ) -> crate::event::DeviceEvent { 29 | use crate::event; 30 | use winit::event::DeviceEvent as W; 31 | match event { 32 | W::Added => event::DeviceAddedEvent { device_id }.into(), 33 | W::Removed => event::DeviceRemovedEvent { device_id }.into(), 34 | W::MouseMotion { delta } => event::DeviceMouseMotionEvent { 35 | device_id, 36 | delta: glam::DVec2::new(delta.0, delta.1).as_vec2(), 37 | } 38 | .into(), 39 | W::MouseWheel { delta } => event::DeviceMouseWheelEvent { device_id, delta }.into(), 40 | W::Motion { axis, value } => event::DeviceMotionEvent { device_id, axis, value }.into(), 41 | W::Button { button, state } => event::DeviceButtonEvent { 42 | device_id, 43 | button, 44 | state: state.into(), 45 | } 46 | .into(), 47 | W::Key(input) => event::DeviceKeyboardInputEvent { 48 | device_id, 49 | input: convert_winit_keyboard_input(input), 50 | } 51 | .into(), 52 | W::Text { codepoint } => event::DeviceTextInputEvent { device_id, codepoint }.into(), 53 | } 54 | } 55 | 56 | pub fn convert_winit_window_event( 57 | window_id: winit::window::WindowId, 58 | event: winit::event::WindowEvent, 59 | mouse_cache: &MouseCache, 60 | ) -> Option { 61 | use crate::event; 62 | use winit::event::WindowEvent as W; 63 | 64 | #[allow(deprecated)] 65 | match event { 66 | W::Ime(_) => None, 67 | W::Occluded(_) => None, 68 | W::Resized(size) => Some(event::WindowResizedEvent { window_id, size: glam::UVec2::new(size.width, size.height) }.into()), 69 | W::Moved(position) => Some(event::WindowMovedEvent { window_id, position: glam::IVec2::new(position.x, position.y) }.into()), 70 | W::CloseRequested => Some(event::WindowCloseRequestedEvent { window_id }.into()), 71 | W::Destroyed => Some(event::WindowDestroyedEvent { window_id }.into()), 72 | W::DroppedFile(file) => Some(event::WindowDroppedFileEvent { window_id, file }.into()), 73 | W::HoveredFile(file) => Some(event::WindowHoveredFileEvent { window_id, file }.into()), 74 | W::HoveredFileCancelled => Some(event::WindowHoveredFileCancelledEvent { window_id }.into()), 75 | W::ReceivedCharacter(character) => Some(event::WindowTextInputEvent { window_id, character }.into()), 76 | W::Focused(true) => Some(event::WindowFocusGainedEvent { window_id }.into()), 77 | W::Focused(false) => Some(event::WindowFocusLostEvent { window_id }.into()), 78 | W::KeyboardInput { 79 | device_id, 80 | input, 81 | is_synthetic, 82 | } => Some( 83 | event::WindowKeyboardInputEvent { 84 | window_id, 85 | device_id, 86 | input: convert_winit_keyboard_input(input), 87 | is_synthetic, 88 | } 89 | .into(), 90 | ), 91 | W::ModifiersChanged(_) => None, 92 | W::CursorMoved { 93 | device_id, 94 | position, 95 | modifiers, 96 | } => { 97 | let position = glam::DVec2::new(position.x, position.y).as_vec2(); 98 | Some(event::WindowMouseMoveEvent { 99 | window_id, 100 | device_id, 101 | position, 102 | prev_position: mouse_cache.get_prev_position(window_id, device_id).unwrap_or(position), 103 | modifiers, 104 | buttons: mouse_cache.get_buttons(device_id).cloned().unwrap_or_default(), 105 | }.into()) 106 | }, 107 | W::CursorEntered { device_id } => Some(event::WindowMouseEnterEvent { 108 | window_id, 109 | device_id, 110 | buttons: mouse_cache.get_buttons(device_id).cloned().unwrap_or_default(), 111 | }.into()), 112 | W::CursorLeft { device_id } => Some(event::WindowMouseLeaveEvent { 113 | window_id, 114 | device_id, 115 | buttons: mouse_cache.get_buttons(device_id).cloned().unwrap_or_default(), 116 | }.into()), 117 | W::MouseWheel { 118 | device_id, 119 | delta, 120 | phase, 121 | modifiers, 122 | } => Some( 123 | event::WindowMouseWheelEvent { 124 | window_id, 125 | device_id, 126 | delta, 127 | phase, 128 | position: mouse_cache.get_position(window_id, device_id), 129 | buttons: mouse_cache.get_buttons(device_id).cloned().unwrap_or_default(), 130 | modifiers, 131 | } 132 | .into(), 133 | ), 134 | W::MouseInput { 135 | device_id, 136 | state, 137 | button, 138 | modifiers, 139 | } => { 140 | let position = mouse_cache.get_position(window_id, device_id)?; 141 | let prev_position = mouse_cache.get_prev_position(window_id, device_id).unwrap_or(position); 142 | Some(event::WindowMouseButtonEvent { 143 | window_id, 144 | device_id, 145 | button: button.into(), 146 | state: state.into(), 147 | position, 148 | prev_position, 149 | buttons: mouse_cache.get_buttons(device_id).cloned().unwrap_or_default(), 150 | modifiers, 151 | }.into()) 152 | }, 153 | W::TouchpadPressure { 154 | device_id, 155 | pressure, 156 | stage, 157 | } => Some( 158 | event::WindowTouchpadPressureEvent { 159 | window_id, 160 | device_id, 161 | pressure, 162 | stage, 163 | } 164 | .into(), 165 | ), 166 | W::AxisMotion { device_id, axis, value } => Some( 167 | event::WindowAxisMotionEvent { 168 | window_id, 169 | device_id, 170 | axis, 171 | value, 172 | } 173 | .into(), 174 | ), 175 | W::Touch(touch) => Some(event::WindowTouchEvent { window_id, touch }.into()), 176 | W::TouchpadMagnify { device_id, delta, phase } => Some( 177 | event::WindowTouchpadMagnifyEvent { 178 | window_id, 179 | device_id, 180 | scale: 1.0 + delta, 181 | phase, 182 | }.into() 183 | ), 184 | W::SmartMagnify { .. } => None, 185 | W::TouchpadRotate { device_id, delta, phase } => Some( 186 | event::WindowTouchpadRotateEvent { 187 | window_id, 188 | device_id, 189 | angle_radians: delta.to_radians().into(), 190 | phase, 191 | }.into() 192 | ), 193 | W::ThemeChanged(theme) => Some( 194 | event::WindowThemeChangedEvent { 195 | window_id, 196 | theme: theme.into(), 197 | } 198 | .into(), 199 | ), 200 | W::ScaleFactorChanged { scale_factor, .. } => Some(event::WindowScaleFactorChangedEvent { window_id, scale_factor }.into()), 201 | } 202 | } 203 | 204 | pub fn convert_winit_keyboard_input(input: winit::event::KeyboardInput) -> crate::event::KeyboardInput { 205 | #[allow(deprecated)] 206 | crate::event::KeyboardInput { 207 | scan_code: input.scancode, 208 | key_code: input.virtual_keycode, 209 | modifiers: input.modifiers, 210 | state: input.state.into(), 211 | } 212 | } 213 | 214 | /// Map a non-user [`Event`] to an [`Event`] with different `UserEvent`. 215 | /// 216 | /// If the event was a [`Event::UserEvent`], it is returned as [`Err`]. 217 | pub fn map_nonuser_event(event: winit::event::Event) -> Result, T> { 218 | use winit::event::Event::*; 219 | match event { 220 | UserEvent(x) => Err(x), 221 | WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }), 222 | DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }), 223 | NewEvents(cause) => Ok(NewEvents(cause)), 224 | MainEventsCleared => Ok(MainEventsCleared), 225 | RedrawRequested(wid) => Ok(RedrawRequested(wid)), 226 | RedrawEventsCleared => Ok(RedrawEventsCleared), 227 | LoopDestroyed => Ok(LoopDestroyed), 228 | Suspended => Ok(Suspended), 229 | Resumed => Ok(Resumed), 230 | } 231 | } 232 | 233 | impl From for crate::event::ElementState { 234 | fn from(other: winit::event::ElementState) -> Self { 235 | match other { 236 | winit::event::ElementState::Pressed => Self::Pressed, 237 | winit::event::ElementState::Released => Self::Released, 238 | } 239 | } 240 | } 241 | 242 | impl From for crate::event::MouseButton { 243 | fn from(other: winit::event::MouseButton) -> Self { 244 | match other { 245 | winit::event::MouseButton::Left => Self::Left, 246 | winit::event::MouseButton::Right => Self::Right, 247 | winit::event::MouseButton::Middle => Self::Middle, 248 | winit::event::MouseButton::Other(x) => Self::Other(x), 249 | } 250 | } 251 | } 252 | 253 | impl From for crate::event::Theme { 254 | fn from(other: winit::window::Theme) -> Self { 255 | match other { 256 | winit::window::Theme::Light => Self::Light, 257 | winit::window::Theme::Dark => Self::Dark, 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | mod context; 2 | mod event; 3 | mod mouse_cache; 4 | mod proxy; 5 | mod util; 6 | mod window; 7 | 8 | pub use context::ContextHandle; 9 | pub use proxy::ContextProxy; 10 | pub use proxy::WindowProxy; 11 | pub use window::WindowHandle; 12 | pub use window::WindowOptions; 13 | 14 | use crate::error; 15 | use context::Context; 16 | use std::panic::{AssertUnwindSafe, catch_unwind}; 17 | 18 | static CONTEXT_PROXY: std::sync::OnceLock = std::sync::OnceLock::new(); 19 | 20 | /// Initialize the global context. 21 | fn initialize_context() -> Result { 22 | let context = Context::new(wgpu::TextureFormat::Bgra8Unorm)?; 23 | if let Err(_) = CONTEXT_PROXY.set(context.proxy.clone()) { 24 | panic!("show_image: context already initialized"); 25 | } 26 | Ok(context) 27 | } 28 | 29 | /// Initialize the global context, or exit the process. 30 | fn initialize_context_or_exit() -> Context { 31 | match initialize_context() { 32 | Ok(x) => x, 33 | Err(crate::error::GetDeviceError::NoSuitableDeviceFound(e)) => { 34 | eprintln!("show-image: Failed to find a suitable device: {}. Terminating process.", e); 35 | std::process::exit(-1); 36 | }, 37 | Err(crate::error::GetDeviceError::NoSuitableAdapterFound(_)) => { 38 | eprintln!("show-image: Failed to find a suitable graphics adapter. Terminating process."); 39 | #[cfg(any(target_os = "android", target_os = "linux"))] 40 | eprintln!("show-image: You may be missing the correct driver. Consider installing the Vulkan driver for your GPU."); 41 | std::process::exit(-2); 42 | } 43 | } 44 | } 45 | 46 | /// Initialize and run the global context and spawn a user task in a new thread. 47 | /// 48 | /// This function never returns. 49 | /// Once the user task finishes, the program exits with status code 0. 50 | /// Any background threads spawned by `show-image` will be joined before the process exits. 51 | /// It is the responsibility of the user code to join any manually spawned tasks. 52 | /// 53 | /// The user task can call the [`context()`] function to obtain a [`ContextProxy`], 54 | /// or the [`create_window()`] function to create a new window directly. 55 | /// 56 | /// If the `macros` feature is enabled, you can also wrap your main function with the [`main`][crate::main] macro 57 | /// instead of manually calling this function. 58 | /// 59 | /// It is also possible to run a user task in the same thread as the context. 60 | /// See [`run_context_with_local_task()`] for more details. 61 | /// 62 | /// # Panics 63 | /// This function panics if initialization of the global context fails. 64 | /// See [`try_run_context`] for a variant that allows the user task to handle these initialization errors. 65 | /// 66 | /// This function also panics if it is called from any thread other than the main thread. 67 | /// Some platforms like OS X require all GUI code to run in the main thread. 68 | /// To ensure portability, this restriction is also enforced on other platforms. 69 | pub fn run_context(user_task: F) -> ! 70 | where 71 | F: FnOnce() -> R + Send + 'static, 72 | R: crate::termination::Termination, 73 | { 74 | let context = initialize_context_or_exit(); 75 | 76 | // Spawn the user task. 77 | std::thread::spawn(move || { 78 | match catch_unwind(AssertUnwindSafe(user_task)) { 79 | Ok(termination) => exit(termination.report()), 80 | Err(_) => { 81 | // Make sure the main thread panics too. 82 | crate::context().run_function(move |_| { 83 | panic!("show-image: main user task panicked"); 84 | }); 85 | }, 86 | } 87 | }); 88 | 89 | context.run(); 90 | } 91 | 92 | /// Initialize and run the global context and spawn a user task in a new thread. 93 | /// 94 | /// This function is almost identical to [`run_context`], 95 | /// except that it allows the user task to handle initialization errors. 96 | /// If the initialization of the global context fails, the user task will be executed in the calling thread. 97 | /// If initialization succeeds, the user task is started in a newly spawned thread. 98 | /// 99 | /// Whether or not initialization succeeded, the process will exit once the user task returns. 100 | /// Any background threads spawned by `show-image` will be joined before the process exits. 101 | /// It is the responsibility of the user code to join any manually spawned tasks. 102 | /// 103 | /// It is also possible to run a user task in the same thread as the context. 104 | /// See [`try_run_context_with_local_task()`] for more details. 105 | /// 106 | /// # Panics 107 | /// This function panics if it is called from any thread other than the main thread. 108 | /// Some platforms like OS X require all GUI code to run in the main thread. 109 | /// To ensure portability, this restriction is also enforced on other platforms. 110 | pub fn try_run_context(user_task: F) -> ! 111 | where 112 | F: FnOnce(Result<(), error::GetDeviceError>) -> R + Send + 'static, 113 | R: crate::termination::Termination, 114 | { 115 | let context = match initialize_context() { 116 | Ok(x) => x, 117 | Err(e) => { 118 | let termination = (user_task)(Err(e)); 119 | std::process::exit(termination.report()); 120 | }, 121 | }; 122 | 123 | // Spawn the user task. 124 | std::thread::spawn(move || { 125 | match catch_unwind(AssertUnwindSafe(move || user_task(Ok(())))) { 126 | Ok(termination) => exit(termination.report()), 127 | Err(_) => { 128 | // Make sure the main thread panics too. 129 | crate::context().run_function(move |_| { 130 | panic!("show-image: main user task panicked"); 131 | }); 132 | }, 133 | } 134 | }); 135 | 136 | context.run(); 137 | } 138 | 139 | /// Initialize and run the global context and run a user task, both in the main thread. 140 | /// 141 | /// The global context will execute the user function in the main thread after the context is fully initialized. 142 | /// 143 | /// This function never returns. 144 | /// The global context will keep running after the local task finishes. 145 | /// It is up to the user code to call [`std::process::exit`] when the process should exit. 146 | /// Alternatively, you could call [`ContextHandle::set_exit_with_last_window`]. 147 | /// 148 | /// *Note*: 149 | /// You should not run a function that blocks for any significant time in the main thread. 150 | /// Doing so will prevent the event loop from processing events and will result in unresponsive windows. 151 | /// 152 | /// If you're looking for a place to run your own application code, 153 | /// you probably want to use [`run_context`] or the [`main`][crate::main] macro. 154 | /// However, if you can drive your entire application from event handlers, 155 | /// then this function is probably what you're looking for. 156 | /// 157 | /// # Panics 158 | /// This function panics if initialization of the global context fails. 159 | /// See [`try_run_context_with_local_task`] for a variant that allows the user task to handle initialization errors. 160 | /// 161 | /// This function also panics if it is called from any thread other than the main thread. 162 | /// Some platforms like OS X require all GUI code to run in the main thread. 163 | /// To ensure portability, this restriction is also enforced on other platforms. 164 | pub fn run_context_with_local_task(user_task: F) -> ! 165 | where 166 | F: FnOnce(&mut ContextHandle) + Send + 'static, 167 | { 168 | let context = initialize_context_or_exit(); 169 | 170 | // Queue the user task. 171 | // It won't be executed until context.run() is called. 172 | context.proxy.run_function(user_task); 173 | context.run(); 174 | } 175 | 176 | /// Initialize and run the global context and run a user task, both in the main thread. 177 | /// 178 | /// This function is almost identical to [`run_context_with_local_task`], 179 | /// except that it allows the user task to handle initialization errors. 180 | /// If the initialization of the global context fails, the process will terminate when the user task returns. 181 | /// Otherwise, the global context will continue running the event loop in the main thread. 182 | /// 183 | /// # Panics 184 | /// This function panics if it is called from any thread other than the main thread. 185 | /// Some platforms like OS X require all GUI code to run in the main thread. 186 | /// To ensure portability, this restriction is also enforced on other platforms. 187 | pub fn try_run_context_with_local_task(user_task: F) -> ! 188 | where 189 | F: FnOnce(Result<&mut ContextHandle, error::GetDeviceError>) + Send + 'static, 190 | { 191 | let context = match initialize_context() { 192 | Ok(x) => x, 193 | Err(e) => { 194 | (user_task)(Err(e)); 195 | std::process::exit(0); 196 | }, 197 | }; 198 | 199 | // Queue the user task. 200 | // It won't be executed until context.run() is called. 201 | context.proxy.run_function(|context| user_task(Ok(context))); 202 | context.run(); 203 | } 204 | 205 | /// Get the global context to interact with existing windows or to create new windows. 206 | /// 207 | /// If you manually spawn threads that try to access the context before calling `run_context`, you introduce a race condition. 208 | /// Instead, you should pass a function to [`run_context`] or one of the variants. 209 | /// Those functions take care to initialize the global context before running the user code. 210 | /// 211 | /// # Panics 212 | /// This panics if the global context is not yet fully initialized. 213 | pub fn context() -> ContextProxy { 214 | match CONTEXT_PROXY.get() { 215 | Some(proxy) => proxy.clone(), 216 | None => panic!("show_image: context not initialized"), 217 | } 218 | } 219 | 220 | /// Create a new window with the global context. 221 | /// 222 | /// If you manually spawn threads that try to access the context before calling `run_context`, you introduce a race condition. 223 | /// Instead, you should pass a function to [`run_context`] that will be started in a new thread after the context is initialized. 224 | /// 225 | /// # Panics 226 | /// This panics if the global context is not yet fully initialized. 227 | pub fn create_window(title: impl Into, options: WindowOptions) -> Result { 228 | let title = title.into(); 229 | context().run_function_wait(move |context| { 230 | let window = context.create_window(title, options)?; 231 | Ok(window.proxy()) 232 | }) 233 | } 234 | 235 | /// Join all background tasks and then exit the process. 236 | /// 237 | /// If you use [`std::process::exit`], running background tasks may be killed. 238 | /// To ensure no data loss occurs, you should use this function instead. 239 | /// 240 | /// Background tasks are spawned when an image is saved through the built-in Ctrl+S or Ctrl+Shift+S shortcut, or by user code. 241 | pub fn exit(code: i32) -> ! { 242 | context().exit(code); 243 | } 244 | -------------------------------------------------------------------------------- /src/backend/mouse_cache.rs: -------------------------------------------------------------------------------- 1 | use winit::event::{ElementState, Event, WindowEvent, DeviceEvent, DeviceId}; 2 | use std::collections::BTreeMap; 3 | 4 | use crate::WindowId; 5 | use crate::event::MouseButtonState; 6 | 7 | #[derive(Default)] 8 | pub struct MouseCache { 9 | mouse_buttons: BTreeMap, 10 | mouse_position: BTreeMap<(WindowId, DeviceId), glam::Vec2>, 11 | mouse_prev_position: BTreeMap<(WindowId, DeviceId), glam::Vec2>, 12 | } 13 | 14 | impl MouseCache { 15 | pub fn get_position(&self, window_id: WindowId, device_id: DeviceId) -> Option { 16 | self.mouse_position.get(&(window_id, device_id)).copied() 17 | } 18 | 19 | pub fn get_prev_position(&self, window_id: WindowId, device_id: DeviceId) -> Option { 20 | self.mouse_prev_position.get(&(window_id, device_id)).copied() 21 | } 22 | 23 | pub fn get_buttons(&self, device_id: DeviceId) -> Option<&MouseButtonState> { 24 | self.mouse_buttons.get(&device_id) 25 | } 26 | 27 | pub fn handle_event(&mut self, event: &Event<()>) { 28 | match event { 29 | Event::WindowEvent { window_id, event } => self.handle_window_event(*window_id, event), 30 | Event::DeviceEvent { device_id, event } => self.handle_device_event(*device_id, event), 31 | _ => (), 32 | } 33 | } 34 | 35 | fn handle_window_event(&mut self, window_id: WindowId, event: &WindowEvent) { 36 | match event { 37 | WindowEvent::MouseInput { device_id, button, state, .. } => { 38 | let buttons = self.mouse_buttons.entry(*device_id).or_default(); 39 | buttons.set_pressed((*button).into(), *state == ElementState::Pressed); 40 | }, 41 | WindowEvent::CursorMoved { device_id, position, .. } => { 42 | let cached_position = self.mouse_position.entry((window_id, *device_id)).or_insert_with(|| [0.0, 0.0].into()); 43 | let cached_prev_position = self.mouse_prev_position.entry((window_id, *device_id)).or_insert_with(|| [0.0, 0.0].into()); 44 | *cached_prev_position = *cached_position; 45 | *cached_position = glam::DVec2::new(position.x, position.y).as_vec2(); 46 | }, 47 | _ => {}, 48 | } 49 | } 50 | 51 | fn handle_device_event(&mut self, device_id: DeviceId, event: &DeviceEvent) { 52 | if let DeviceEvent::Removed = event { 53 | self.remove_device(device_id) 54 | } 55 | } 56 | 57 | fn remove_device(&mut self, device_id: DeviceId) { 58 | self.mouse_buttons.remove(&device_id); 59 | let keys: Vec<_> = self.mouse_position.keys().filter(|(_, x)| *x == device_id).copied().collect(); 60 | for key in &keys { 61 | self.mouse_position.remove(key); 62 | } 63 | let keys: Vec<_> = self.mouse_prev_position.keys().filter(|(_, x)| *x == device_id).copied().collect(); 64 | for key in &keys { 65 | self.mouse_prev_position.remove(key); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/backend/proxy.rs: -------------------------------------------------------------------------------- 1 | use crate::ContextHandle; 2 | use crate::Image; 3 | use crate::WindowHandle; 4 | use crate::WindowId; 5 | use crate::error::{InvalidWindowId, SetImageError}; 6 | use crate::event::Event; 7 | use crate::event::EventHandlerControlFlow; 8 | use crate::event::WindowEvent; 9 | use crate::oneshot; 10 | 11 | use std::sync::mpsc; 12 | 13 | /// Proxy object to interact with a window from a user thread. 14 | /// 15 | /// The proxy object only exposes a small subset of the functionality of a window. 16 | /// However, you can use [`run_function()`][Self::run_function] 17 | /// to get access to the underlying [`WindowHandle`] from the context thread. 18 | /// With [`run_function_wait()`][Self::run_function_wait`] you can also get the return value of the function back: 19 | /// 20 | /// ```no_run 21 | /// # fn foo(window_proxy: show_image::WindowProxy) -> Result<(), show_image::error::InvalidWindowId> { 22 | /// let inner_size = window_proxy.run_function_wait(|window| window.inner_size())?; 23 | /// # Ok(()) 24 | /// # } 25 | /// ``` 26 | /// 27 | /// You should not use proxy objects from withing the global context thread. 28 | /// The proxy objects often wait for the global context to perform some action. 29 | /// Doing so from within the global context thread would cause a deadlock. 30 | #[derive(Clone)] 31 | pub struct WindowProxy { 32 | window_id: WindowId, 33 | context_proxy: ContextProxy, 34 | } 35 | 36 | /// Proxy object to interact with the global context from a user thread. 37 | /// 38 | /// You should not use proxy objects from withing the global context thread. 39 | /// The proxy objects often wait for the global context to perform some action. 40 | /// Doing so from within the global context thread would cause a deadlock. 41 | #[derive(Clone)] 42 | pub struct ContextProxy { 43 | event_loop: EventLoopProxy, 44 | context_thread: std::thread::ThreadId, 45 | } 46 | 47 | /// Dynamic function that can be run by the global context. 48 | pub type ContextFunction = Box; 49 | 50 | /// Internal shorthand for the correct `winit::event::EventLoopProxy`. 51 | /// 52 | /// Not for use in public APIs. 53 | type EventLoopProxy = winit::event_loop::EventLoopProxy; 54 | 55 | impl ContextProxy { 56 | /// Wrap an [`EventLoopProxy`] in a [`ContextProxy`]. 57 | pub(crate) fn new(event_loop: EventLoopProxy, context_thread: std::thread::ThreadId) -> Self { 58 | Self { 59 | event_loop, 60 | context_thread, 61 | } 62 | } 63 | 64 | /// Add a global event handler to the context. 65 | /// 66 | /// Events that are already queued with the event loop will not be passed to the handler. 67 | /// 68 | /// This function uses [`Self::run_function_wait`] internally, so it blocks until the event handler is added. 69 | /// To avoid blocking, you can use [`Self::run_function`] to post a lambda that adds an error handler instead. 70 | /// 71 | /// # Panics 72 | /// This function will panic if called from within the context thread. 73 | pub fn add_event_handler(&self, handler: F) 74 | where 75 | F: FnMut(&mut ContextHandle, &mut Event, &mut EventHandlerControlFlow) + Send + 'static, 76 | { 77 | self.run_function_wait(move |context| context.add_event_handler(handler)) 78 | } 79 | 80 | /// Add an event handler for a specific window. 81 | /// 82 | /// Events that are already queued with the event loop will not be passed to the handler. 83 | /// 84 | /// This function uses [`Self::run_function_wait`] internally, so it blocks until the event handler is added. 85 | /// To avoid blocking, you can use [`Self::run_function`] to post a lambda that adds an error handler instead. 86 | /// 87 | /// # Panics 88 | /// This function will panic if called from within the context thread. 89 | pub fn add_window_event_handler(&self, window_id: WindowId, handler: F) -> Result<(), InvalidWindowId> 90 | where 91 | F: FnMut(WindowHandle, &mut WindowEvent, &mut EventHandlerControlFlow) + Send + 'static, 92 | { 93 | self.run_function_wait(move |context| { 94 | let mut window = context.window(window_id)?; 95 | window.add_event_handler(handler); 96 | Ok(()) 97 | }) 98 | } 99 | 100 | /// Post a function for execution in the context thread without waiting for it to execute. 101 | /// 102 | /// This function returns immediately, without waiting for the posted function to start or complete. 103 | /// If you want to get a return value back from the function, use [`Self::run_function_wait`] instead. 104 | /// 105 | /// *Note:* 106 | /// You should not post functions to the context thread that block for a long time. 107 | /// Doing so will block the event loop and will make the windows unresponsive until the event loop can continue. 108 | /// Consider using [`Self::run_background_task`] for long blocking tasks instead. 109 | /// 110 | /// # Panics 111 | /// This function will panic if called from within the context thread. 112 | pub fn run_function(&self, function: F) 113 | where 114 | F: 'static + FnOnce(&mut ContextHandle) + Send, 115 | { 116 | let function = Box::new(function); 117 | if self.event_loop.send_event(function).is_err() { 118 | panic!("global context stopped running but somehow the process is still alive"); 119 | } 120 | } 121 | 122 | /// Post a function for execution in the context thread and wait for the return value. 123 | /// 124 | /// If you do not need a return value from the posted function, 125 | /// you can use [`Self::run_function`] to avoid blocking the calling thread until it completes. 126 | /// 127 | /// *Note:* 128 | /// You should not post functions to the context thread that block for a long time. 129 | /// Doing so will block the event loop and will make the windows unresponsive until the event loop can continue. 130 | /// Consider using [`Self::run_background_task`] for long blocking tasks instead. 131 | /// 132 | /// # Panics 133 | /// This function will panic if called from within the context thread. 134 | pub fn run_function_wait(&self, function: F) -> T 135 | where 136 | F: FnOnce(&mut ContextHandle) -> T + Send + 'static, 137 | T: Send + 'static, 138 | { 139 | self.assert_thread(); 140 | 141 | let (result_tx, result_rx) = oneshot::channel(); 142 | self.run_function(move |context| result_tx.send((function)(context))); 143 | result_rx.recv() 144 | .expect("global context failed to send function return value back, which can only happen if the event loop stopped, but that should also kill the process") 145 | } 146 | 147 | /// Run a task in a background thread and register it with the context. 148 | /// 149 | /// The task will be executed in a different thread than the context. 150 | /// Currently, each task is spawned in a separate thread. 151 | /// In the future, tasks may be run in a dedicated thread pool. 152 | /// 153 | /// The background task will be joined before the process is terminated when you use [`Self::exit()`] or one of the other exit functions of this crate. 154 | pub fn run_background_task(&self, task: F) 155 | where 156 | F: FnOnce() + Send + 'static, 157 | { 158 | self.run_function(move |context| { 159 | context.run_background_task(task); 160 | }); 161 | } 162 | 163 | /// Create a channel that receives events from the context. 164 | /// 165 | /// To close the channel, simply drop de receiver. 166 | /// 167 | /// *Warning:* 168 | /// The created channel blocks when you request an event until one is available. 169 | /// You should never use the receiver from within an event handler or a function posted to the global context thread. 170 | /// Doing so would cause a deadlock. 171 | /// 172 | /// # Panics 173 | /// This function will panic if called from within the context thread. 174 | pub fn event_channel(&self) -> mpsc::Receiver { 175 | let (tx, rx) = mpsc::channel(); 176 | self.add_event_handler(move |_context, event, control| { 177 | // If the receiver is dropped, remove the handler. 178 | if tx.send(event.clone()).is_err() { 179 | control.remove_handler = true; 180 | } 181 | }); 182 | 183 | rx 184 | } 185 | 186 | /// Create a channel that receives events from a window. 187 | /// 188 | /// To close the channel, simply drop de receiver. 189 | /// The channel is closed automatically when the window is destroyed. 190 | /// 191 | /// *Warning:* 192 | /// The created channel blocks when you request an event until one is available. 193 | /// You should never use the receiver from within an event handler or a function posted to the global context thread. 194 | /// Doing so would cause a deadlock. 195 | /// 196 | /// # Panics 197 | /// This function will panic if called from within the context thread. 198 | pub fn window_event_channel(&self, window_id: WindowId) -> Result, InvalidWindowId> { 199 | let (tx, rx) = mpsc::channel(); 200 | self.add_window_event_handler(window_id, move |_window, event, control| { 201 | // If the receiver is dropped, remove the handler. 202 | if tx.send(event.clone()).is_err() { 203 | control.remove_handler = true; 204 | } 205 | })?; 206 | Ok(rx) 207 | } 208 | 209 | /// Join all background tasks and then exit the process. 210 | /// 211 | /// If you use [`std::process::exit`], running background tasks may be killed. 212 | /// To ensure no data loss occurs, you should use this function instead. 213 | /// 214 | /// Background tasks are spawned when an image is saved through the built-in Ctrl+S or Ctrl+Shift+S shortcut, or by user code. 215 | /// 216 | /// # Panics 217 | /// This function will panic if called from within the context thread. 218 | pub fn exit(&self, code: i32) -> ! { 219 | self.assert_thread(); 220 | self.run_function(move |context| context.exit(code)); 221 | loop { 222 | std::thread::park(); 223 | } 224 | } 225 | 226 | /// Check that the current thread is not running the context event loop. 227 | /// 228 | /// # Panics 229 | /// This function will panic if called from within the context thread. 230 | #[track_caller] 231 | fn assert_thread(&self) { 232 | if std::thread::current().id() == self.context_thread { 233 | panic!("ContextProxy used from within the context thread, which would cause a deadlock. Use ContextHandle instead."); 234 | } 235 | } 236 | } 237 | 238 | impl WindowProxy { 239 | /// Create a new window proxy from a context proxy and a window ID. 240 | pub fn new(window_id: WindowId, context_proxy: ContextProxy) -> Self { 241 | Self { window_id, context_proxy } 242 | } 243 | 244 | /// Get the window ID. 245 | pub fn id(&self) -> WindowId { 246 | self.window_id 247 | } 248 | 249 | /// Get the context proxy of the window proxy. 250 | pub fn context_proxy(&self) -> &ContextProxy { 251 | &self.context_proxy 252 | } 253 | 254 | /// Set the displayed image of the window. 255 | /// 256 | /// The real work is done in the context thread. 257 | /// This function blocks until the context thread has performed the action. 258 | /// 259 | /// Note that you can not change the overlays with this function. 260 | /// To modify those, you can use [`Self::run_function`] or [`Self::run_function_wait`] 261 | /// to get access to the [`WindowHandle`]. 262 | /// 263 | /// # Panics 264 | /// This function will panic if called from within the context thread. 265 | pub fn set_image(&self, name: impl Into, image: impl Into) -> Result<(), SetImageError> { 266 | let name = name.into(); 267 | let image = image.into(); 268 | self.run_function_wait(move |mut window| -> Result<(), SetImageError> { 269 | window.set_image(name, &image.as_image_view()?); 270 | Ok(()) 271 | })? 272 | } 273 | 274 | /// Add an event handler for the window. 275 | /// 276 | /// Events that are already queued with the event loop will not be passed to the handler. 277 | /// 278 | /// This function uses [`ContextProxy::run_function_wait`] internally, so it blocks until the event handler is added. 279 | /// To avoid blocking, you can use [`ContextProxy::run_function`] to post a lambda that adds an event handler instead. 280 | /// 281 | /// # Panics 282 | /// This function will panic if called from within the context thread. 283 | pub fn add_event_handler(&self, handler: F) -> Result<(), InvalidWindowId> 284 | where 285 | F: FnMut(WindowHandle, &mut WindowEvent, &mut EventHandlerControlFlow) + Send + 'static, 286 | { 287 | self.context_proxy.add_window_event_handler(self.window_id, handler) 288 | } 289 | 290 | /// Create a channel that receives events from the window. 291 | /// 292 | /// To close the channel, simply drop de receiver. 293 | /// The channel is closed automatically when the window is destroyed. 294 | /// 295 | /// *Warning:* 296 | /// The created channel blocks when you request an event until one is available. 297 | /// You should never use the receiver from within an event handler or a function posted to the global context thread. 298 | /// Doing so would cause a deadlock. 299 | /// 300 | /// # Panics 301 | /// This function will panic if called from within the context thread. 302 | pub fn event_channel(&self) -> Result, InvalidWindowId> { 303 | self.context_proxy.window_event_channel(self.window_id) 304 | } 305 | 306 | /// Wait for the window to be destroyed. 307 | /// 308 | /// This can happen if the application code destroys the window or if the user closes the window. 309 | /// 310 | /// *Warning:* 311 | /// This function blocks until the window is closed. 312 | /// You should never use this function from within an event handler or a function posted to the global context thread. 313 | /// Doing so would cause a deadlock. 314 | /// 315 | /// # Panics 316 | /// This function will panic if called from within the context thread. 317 | pub fn wait_until_destroyed(&self) -> Result<(), InvalidWindowId> { 318 | let (tx, rx) = oneshot::channel::<()>(); 319 | self.add_event_handler(move |_window, _event, _control| { 320 | // Need to mention the tx half so it gets moved into the closure. 321 | let _tx = &tx; 322 | })?; 323 | 324 | // We actually want to wait for the transmit handle to be dropped, so ignore receive errors. 325 | let _ = rx.recv(); 326 | Ok(()) 327 | } 328 | 329 | /// Post a function for execution in the context thread without waiting for it to execute. 330 | /// 331 | /// This function returns immediately, without waiting for the posted function to start or complete. 332 | /// If you want to get a return value back from the function, use [`Self::run_function_wait`] instead. 333 | /// 334 | /// *Note:* 335 | /// You should not use this to post functions that block for a long time. 336 | /// Doing so will block the event loop and will make the windows unresponsive until the event loop can continue. 337 | /// Consider using [`self.context_proxy().run_background_task(...)`][ContextProxy::run_background_task] for long blocking tasks instead. 338 | /// 339 | /// # Panics 340 | /// This function will panic if called from within the context thread. 341 | pub fn run_function(&self, function: F) 342 | where 343 | F: 'static + FnOnce(WindowHandle) + Send, 344 | { 345 | let window_id = self.window_id; 346 | self.context_proxy.run_function(move |context| { 347 | if let Ok(window) = context.window(window_id) { 348 | function(window); 349 | } 350 | }) 351 | } 352 | 353 | /// Post a function for execution in the context thread and wait for the return value. 354 | /// 355 | /// If you do not need a return value from the posted function, 356 | /// you can use [`Self::run_function`] to avoid blocking the calling thread until it completes. 357 | /// 358 | /// *Note:* 359 | /// You should not use this to post functions that block for a long time. 360 | /// Doing so will block the event loop and will make the windows unresponsive until the event loop can continue. 361 | /// Consider using [`self.context_proxy().run_background_task(...)`][ContextProxy::run_background_task] for long blocking tasks instead. 362 | /// 363 | /// # Panics 364 | /// This function will panic if called from within the context thread. 365 | pub fn run_function_wait(&self, function: F) -> Result 366 | where 367 | F: FnOnce(WindowHandle) -> T + Send + 'static, 368 | T: Send + 'static, 369 | { 370 | let window_id = self.window_id; 371 | self.context_proxy.run_function_wait(move |context| { 372 | let window = context.window(window_id)?; 373 | Ok(function(window)) 374 | }) 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /src/backend/util/buffer.rs: -------------------------------------------------------------------------------- 1 | /// Reinterpret an object as bytes. 2 | unsafe fn as_bytes(value: &T) -> &[u8] { 3 | std::slice::from_raw_parts(value as *const T as *const u8, std::mem::size_of_val(value)) 4 | } 5 | 6 | /// Create a [`wgpu::Buffer`] with an arbitrary object as contents. 7 | pub fn create_buffer_with_value(device: &wgpu::Device, label: Option<&str>, value: &T, usage: wgpu::BufferUsages) -> wgpu::Buffer { 8 | use wgpu::util::DeviceExt; 9 | unsafe { 10 | let contents = as_bytes(value); 11 | device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label, contents, usage }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/backend/util/gpu_image.rs: -------------------------------------------------------------------------------- 1 | use crate::ImageInfo; 2 | use crate::ImageView; 3 | use crate::{Alpha, PixelFormat}; 4 | use super::create_buffer_with_value; 5 | 6 | /// A GPU image buffer ready to be used with the rendering pipeline. 7 | pub struct GpuImage { 8 | name: String, 9 | info: ImageInfo, 10 | bind_group: wgpu::BindGroup, 11 | _uniforms: wgpu::Buffer, 12 | _data: wgpu::Buffer, 13 | } 14 | 15 | /// The uniforms associated with a [`GpuImage`]. 16 | #[derive(Debug, Copy, Clone)] 17 | #[allow(unused)] // All fields are used by the GPU. 18 | pub struct GpuImageUniforms { 19 | format: u32, 20 | width: u32, 21 | height: u32, 22 | stride_x: u32, 23 | stride_y: u32, 24 | } 25 | 26 | impl GpuImage { 27 | /// Create a [`GpuImage`] from an image buffer. 28 | pub fn from_data(name: String, device: &wgpu::Device, bind_group_layout: &wgpu::BindGroupLayout, image: &ImageView) -> Self { 29 | let info = image.info(); 30 | 31 | let format = match info.pixel_format { 32 | PixelFormat::Mono8 => 0, 33 | PixelFormat::MonoAlpha8(Alpha::Unpremultiplied) => 1, 34 | PixelFormat::MonoAlpha8(Alpha::Premultiplied) => 2, 35 | PixelFormat::Bgr8 => 3, 36 | PixelFormat::Bgra8(Alpha::Unpremultiplied) => 4, 37 | PixelFormat::Bgra8(Alpha::Premultiplied) => 5, 38 | PixelFormat::Rgb8 => 6, 39 | PixelFormat::Rgba8(Alpha::Unpremultiplied) => 7, 40 | PixelFormat::Rgba8(Alpha::Premultiplied) => 8, 41 | }; 42 | 43 | let uniforms = GpuImageUniforms { 44 | format, 45 | width: info.size.x, 46 | height: info.size.y, 47 | stride_x: info.stride.x, 48 | stride_y: info.stride.y, 49 | }; 50 | 51 | let uniforms = create_buffer_with_value( 52 | device, 53 | Some(&format!("{}_uniforms_buffer", name)), 54 | &uniforms, 55 | wgpu::BufferUsages::UNIFORM, 56 | ); 57 | 58 | use wgpu::util::DeviceExt; 59 | let data = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 60 | label: Some(&format!("{}_image_buffer", name)), 61 | contents: image.data(), 62 | usage: wgpu::BufferUsages::STORAGE, 63 | }); 64 | 65 | let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { 66 | label: Some(&format!("{}_bind_group", name)), 67 | layout: bind_group_layout, 68 | entries: &[ 69 | wgpu::BindGroupEntry { 70 | binding: 0, 71 | resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { 72 | buffer: &uniforms, 73 | offset: 0, 74 | size: None, // Use entire buffer. 75 | }), 76 | }, 77 | wgpu::BindGroupEntry { 78 | binding: 1, 79 | resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { 80 | buffer: &data, 81 | offset: 0, 82 | size: None, // Use entire buffer. 83 | }), 84 | }, 85 | ], 86 | }); 87 | 88 | Self { 89 | name, 90 | info, 91 | bind_group, 92 | _uniforms: uniforms, 93 | _data: data, 94 | } 95 | } 96 | 97 | /// Get the name of the image. 98 | #[allow(unused)] 99 | pub fn name(&self) -> &str { 100 | &self.name 101 | } 102 | 103 | /// Get the image info. 104 | pub fn info(&self) -> &ImageInfo { 105 | &self.info 106 | } 107 | 108 | /// Get the bind group that should be used to render the image with the rendering pipeline. 109 | pub fn bind_group(&self) -> &wgpu::BindGroup { 110 | &self.bind_group 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/backend/util/map_buffer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | /// Synchronously wait for a buffer to be mappable. 4 | fn wait_for_buffer( 5 | device: &wgpu::Device, 6 | buffer: wgpu::BufferSlice<'_>, 7 | map_mode: wgpu::MapMode, 8 | ) -> Result<(), wgpu::BufferAsyncError> { 9 | let result = Arc::new(Mutex::new(None)); 10 | buffer.map_async(map_mode, { 11 | let result = result.clone(); 12 | move |new_result| { 13 | *result.lock().unwrap() = Some(new_result); 14 | } 15 | }); 16 | 17 | loop { 18 | device.poll(wgpu::Maintain::Wait); 19 | if let Some(result) = result.lock().unwrap().take() { 20 | return result; 21 | } 22 | } 23 | } 24 | 25 | /// Synchronously map a buffer for read access. 26 | /// 27 | /// This will internally call [`wgpu::Device::poll()`] until the buffer is ready, and then map it. 28 | #[allow(unused)] 29 | pub fn map_buffer<'a>(device: &wgpu::Device, buffer: wgpu::BufferSlice<'a>) -> Result, wgpu::BufferAsyncError> { 30 | wait_for_buffer(device, buffer, wgpu::MapMode::Read)?; 31 | Ok(buffer.get_mapped_range()) 32 | } 33 | -------------------------------------------------------------------------------- /src/backend/util/mod.rs: -------------------------------------------------------------------------------- 1 | mod buffer; 2 | mod gpu_image; 3 | #[cfg(feature = "save")] 4 | mod map_buffer; 5 | mod retain_mut; 6 | mod uniforms_buffer; 7 | 8 | pub use buffer::create_buffer_with_value; 9 | pub use gpu_image::GpuImage; 10 | pub use gpu_image::GpuImageUniforms; 11 | #[cfg(feature = "save")] 12 | pub use map_buffer::map_buffer; 13 | pub use retain_mut::RetainMut; 14 | pub use uniforms_buffer::{ToStd140, UniformsBuffer}; 15 | -------------------------------------------------------------------------------- /src/backend/util/retain_mut.rs: -------------------------------------------------------------------------------- 1 | pub trait RetainMut { 2 | /// Same as [`Vec::retain`], except the lamba receives mutable references. 3 | fn retain_mut(&mut self, f: F) 4 | where 5 | F: FnMut(&mut T) -> bool; 6 | } 7 | 8 | impl RetainMut for Vec { 9 | fn retain_mut(&mut self, mut f: F) 10 | where 11 | F: FnMut(&mut T) -> bool, 12 | { 13 | let len = self.len(); 14 | let mut del = 0; 15 | { 16 | let v = &mut **self; 17 | 18 | for i in 0..len { 19 | if !f(&mut v[i]) { 20 | del += 1; 21 | } else if del > 0 { 22 | v.swap(i - del, i); 23 | } 24 | } 25 | } 26 | if del > 0 { 27 | self.truncate(len - del); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/backend/util/uniforms_buffer.rs: -------------------------------------------------------------------------------- 1 | use super::buffer::create_buffer_with_value; 2 | 3 | /// Trait for data in Std140 compatible layout. 4 | /// 5 | /// # Safety 6 | /// Implementing this trait indicates that the data is in Std140 compatible layout. 7 | /// If that is not true, the GPU may perform illegal memory access. 8 | pub unsafe trait ToStd140 { 9 | type Output: Copy; 10 | 11 | const STD140_SIZE: u64 = std::mem::size_of::() as u64; 12 | 13 | fn to_std140(&self) -> Self::Output; 14 | } 15 | 16 | /// A buffer holding uniform data and matching bind group. 17 | /// 18 | /// The buffer can be marked as dirty to indicate the contents need to be updated. 19 | /// The contents can be updated with [`Self::update_from`], 20 | /// which will also clear the dirty flag. 21 | pub struct UniformsBuffer { 22 | buffer: wgpu::Buffer, 23 | bind_group: wgpu::BindGroup, 24 | dirty: bool, 25 | _phantom: std::marker::PhantomData, 26 | } 27 | 28 | impl UniformsBuffer { 29 | /// Create a new UniformsBuffer from the given value and bind group layout. 30 | /// 31 | /// The bind group layout must have exactly 1 binding for a buffer at index 0. 32 | pub fn from_value(device: &wgpu::Device, value: &T, layout: &wgpu::BindGroupLayout) -> Self { 33 | let buffer = create_buffer_with_value(device, None, &value.to_std140(), wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST); 34 | let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { 35 | label: Some("uniforms_bind_group"), 36 | layout, 37 | entries: &[wgpu::BindGroupEntry { 38 | binding: 0, 39 | resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { 40 | buffer: &buffer, 41 | offset: 0, 42 | size: None, // Use entire buffer. 43 | }), 44 | }], 45 | }); 46 | 47 | Self { 48 | buffer, 49 | bind_group, 50 | dirty: false, 51 | _phantom: std::marker::PhantomData, 52 | } 53 | } 54 | 55 | /// Get the bind group for the uniforms. 56 | pub fn bind_group(&self) -> &wgpu::BindGroup { 57 | &self.bind_group 58 | } 59 | 60 | /// Check if the uniforms are marked as dirty. 61 | pub fn is_dirty(&self) -> bool { 62 | self.dirty 63 | } 64 | 65 | /// Mark the uniforms as dirty. 66 | pub fn mark_dirty(&mut self, dirty: bool) { 67 | self.dirty = dirty; 68 | } 69 | 70 | /// Update the buffer contents using the provided command encoder and clear the dirty flag. 71 | pub fn update_from(&mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, value: &T) { 72 | let buffer = create_buffer_with_value(device, None, &value.to_std140(), wgpu::BufferUsages::COPY_SRC); 73 | encoder.copy_buffer_to_buffer(&buffer, 0, &self.buffer, 0, T::STD140_SIZE as wgpu::BufferAddress); 74 | self.mark_dirty(false); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/backend/window.rs: -------------------------------------------------------------------------------- 1 | use crate::Color; 2 | use crate::ContextHandle; 3 | use crate::ImageInfo; 4 | use crate::ImageView; 5 | use crate::WindowId; 6 | use crate::WindowProxy; 7 | use crate::backend::Context; 8 | use crate::backend::util::GpuImage; 9 | use crate::backend::util::UniformsBuffer; 10 | use crate::error; 11 | use crate::event::EventHandlerControlFlow; 12 | use crate::event::WindowEvent; 13 | use glam::Vec3; 14 | use glam::{Affine2, Vec2}; 15 | use indexmap::IndexMap; 16 | 17 | /// Internal shorthand for window event handlers. 18 | type DynWindowEventHandler = dyn FnMut(WindowHandle, &mut WindowEvent, &mut EventHandlerControlFlow); 19 | 20 | /// Window capable of displaying images using wgpu. 21 | pub(crate) struct Window { 22 | /// The winit window. 23 | pub window: winit::window::Window, 24 | 25 | /// If true, preserve the aspect ratio of images. 26 | pub preserve_aspect_ratio: bool, 27 | 28 | /// The background color of the window. 29 | pub background_color: Color, 30 | 31 | /// The wgpu surface to render to. 32 | pub surface: wgpu::Surface, 33 | 34 | /// The window specific uniforms for the render pipeline. 35 | pub uniforms: UniformsBuffer, 36 | 37 | /// The image to display (if any). 38 | pub image: Option, 39 | 40 | /// Overlays for the window. 41 | pub overlays: IndexMap, 42 | 43 | /// Transformation to apply to the image, in virtual window space. 44 | /// 45 | /// Virtual window space goes from (0, 0) in the top left to (1, 1) in the bottom right. 46 | pub user_transform: Affine2, 47 | 48 | /// The event handlers for this specific window. 49 | pub event_handlers: Vec>, 50 | } 51 | 52 | /// An overlay added to a window. 53 | pub(crate) struct Overlay { 54 | /// The image to show. 55 | pub image: GpuImage, 56 | 57 | /// If true, show the overlay, otherwise do not. 58 | pub visible: bool, 59 | } 60 | 61 | /// Handle to a window. 62 | /// 63 | /// A [`WindowHandle`] can be used to interact with a window from within the global context thread. 64 | /// To interact with a window from another thread, you need a [`WindowProxy`]. 65 | pub struct WindowHandle<'a> { 66 | /// The context handle to use. 67 | context_handle: ContextHandle<'a>, 68 | 69 | /// The index of the window in [`Context::windows`]. 70 | index: usize, 71 | 72 | /// Flag to signal to the handle creator that the window was destroyed. 73 | destroy_flag: Option<&'a mut bool>, 74 | } 75 | 76 | impl<'a> WindowHandle<'a> { 77 | /// Create a new window handle from a context handle and a window ID. 78 | pub fn new(context_handle: ContextHandle<'a>, index: usize, destroy_flag: Option<&'a mut bool>) -> Self { 79 | Self { context_handle, index, destroy_flag } 80 | } 81 | 82 | /// Get a reference to the context. 83 | fn context(&self) -> &Context { 84 | self.context_handle().context 85 | } 86 | 87 | /// Get a mutable reference to the context. 88 | /// 89 | /// # Safety 90 | /// The current window may not be moved or removed through the returned reference. 91 | /// In practise, this means that you may not create or destroy any windows. 92 | unsafe fn context_mut(&mut self) -> &mut Context { 93 | self.context_handle.context 94 | } 95 | 96 | /// Get a reference to the window. 97 | fn window(&self) -> &Window { 98 | &self.context().windows[self.index] 99 | } 100 | 101 | /// Get a mutable reference to the window. 102 | fn window_mut(&mut self) -> &mut Window { 103 | let index = self.index; 104 | unsafe { &mut self.context_mut().windows[index] } 105 | } 106 | 107 | /// Get the window ID. 108 | pub fn id(&self) -> WindowId { 109 | self.window().id() 110 | } 111 | 112 | /// Get a proxy object for the window to interact with it from a different thread. 113 | /// 114 | /// You should not use proxy objects from withing the global context thread. 115 | /// The proxy objects often wait for the global context to perform some action. 116 | /// Doing so from within the global context thread would cause a deadlock. 117 | pub fn proxy(&self) -> WindowProxy { 118 | WindowProxy::new(self.id(), self.context_handle.proxy()) 119 | } 120 | 121 | /// Release the window handle to get a [`ContextHandle`]. 122 | /// 123 | /// This can be used inside a window event handler to gain access to the [`ContextHandle`]. 124 | /// If you do not need mutable access to the context, you can also use [`context_handle()`](Self::context_handle). 125 | pub fn release(self) -> ContextHandle<'a> { 126 | self.context_handle 127 | } 128 | 129 | /// Get a reference to the context handle. 130 | /// 131 | /// If you need mutable access to the context, use [`release()`](Self::release) instead. 132 | pub fn context_handle(&self) -> &ContextHandle<'a> { 133 | &self.context_handle 134 | } 135 | 136 | /// Destroy the window. 137 | /// 138 | /// Any subsequent operation on the window through an existing [`WindowProxy`] will return [`InvalidWindowId`](crate::error::InvalidWindowId). 139 | pub fn destroy(self) -> ContextHandle<'a> { 140 | let WindowHandle { context_handle, index, destroy_flag } = self; 141 | context_handle.context.windows.remove(index); 142 | if let Some(destroy_flag) = destroy_flag { 143 | *destroy_flag = true; 144 | } 145 | context_handle 146 | } 147 | 148 | /// Set the title of the window. 149 | pub fn set_title(&self, title: impl AsRef) { 150 | self.window().window.set_title(title.as_ref()); 151 | } 152 | 153 | /// Get the image info. 154 | /// 155 | /// Returns [`None`] if no image is set for the window. 156 | pub fn image_info(&self) -> Option<&ImageInfo> { 157 | Some(self.window().image.as_ref()?.info()) 158 | } 159 | 160 | /// Check if the window will preserve the aspect ratio of images it displays. 161 | pub fn preserve_aspect_ratio(&self) -> bool { 162 | self.window().preserve_aspect_ratio 163 | } 164 | 165 | /// Set if the window will preserve the aspect ratio of images it displays. 166 | pub fn set_preserve_aspect_ratio(&mut self, preserve_aspect_ratio: bool) { 167 | self.window_mut().preserve_aspect_ratio = preserve_aspect_ratio; 168 | self.window().window.request_redraw(); 169 | } 170 | 171 | /// Get the background color of the window. 172 | pub fn background_color(&self) -> Color { 173 | self.window().background_color 174 | } 175 | 176 | /// Set the background color of the window. 177 | pub fn set_background_color(&mut self, background_color: Color) { 178 | self.window_mut().background_color = background_color; 179 | self.window().window.request_redraw(); 180 | } 181 | 182 | /// Make the window visible or invisible. 183 | pub fn set_visible(&mut self, visible: bool) { 184 | self.window_mut().set_visible(visible); 185 | self.window().window.request_redraw(); 186 | } 187 | 188 | /// Set the window position in pixels. 189 | /// 190 | /// This will automatically un-maximize the window. 191 | /// 192 | /// Some window managers or platforms may ignore this property. 193 | pub fn set_outer_position(&self, position: impl Into) { 194 | let position = position.into(); 195 | self.window().window.set_outer_position(winit::dpi::PhysicalPosition::new(position.x, position.y)); 196 | } 197 | 198 | /// Get the inner size of the window in physical pixels. 199 | /// 200 | /// This returns the size of the window contents, excluding borders, the title bar and other decorations. 201 | pub fn inner_size(&self) -> glam::UVec2 { 202 | let size = self.window().window.inner_size(); 203 | glam::UVec2::new(size.width, size.height) 204 | } 205 | 206 | /// Get the outer size of the window in physical pixels. 207 | /// 208 | /// This returns the size of the entire window, including borders, the title bar and other decorations. 209 | pub fn outer_size(&self) -> glam::UVec2 { 210 | let size = self.window().window.outer_size(); 211 | glam::UVec2::new(size.width, size.height) 212 | } 213 | 214 | /// Set the inner size of the window in pixels. 215 | /// 216 | /// The size is excluding borders, the title bar and other decorations. 217 | /// 218 | /// Some window managers may ignore this property. 219 | pub fn set_inner_size(&mut self, size: impl Into) { 220 | let size = size.into(); 221 | self.window_mut().window.set_inner_size(winit::dpi::PhysicalSize::new(size.x, size.y)); 222 | self.window().window.request_redraw(); 223 | } 224 | 225 | /// Set if the window should be resizable for the user. 226 | /// 227 | /// Some window managers may ignore this property. 228 | pub fn set_resizable(&mut self, resizable: bool) { 229 | self.window().window.set_resizable(resizable); 230 | } 231 | 232 | /// Set if the window should be drawn without borders. 233 | /// 234 | /// Some window managers may ignore this property. 235 | pub fn set_borderless(&mut self, borderless: bool) { 236 | self.window().window.set_decorations(!borderless); 237 | } 238 | 239 | /// Set the window in fullscreen mode or back. 240 | /// 241 | /// This will set the window to borderless fullscreen on the current monitor or back. 242 | /// Fullscreen is set if the argument is `true`, otherwise the window is returned to normal size. 243 | /// 244 | /// Some window managers may ignore this property. 245 | pub fn set_fullscreen(&mut self, fullscreen: bool) { 246 | let opt = if fullscreen { 247 | Some(winit::window::Fullscreen::Borderless(None)) 248 | } else { 249 | None 250 | }; 251 | self.window().window.set_fullscreen(opt); 252 | } 253 | 254 | /// Check if the window is set to fullscreen mode. 255 | /// 256 | /// Note that some window managers may ignore the request for fullscreen mode. 257 | /// In that case, this function may return true while the window is not displayed in fullscreen mode. 258 | pub fn is_fullscreen(&self) -> bool { 259 | self.window().window.fullscreen().is_some() 260 | } 261 | 262 | /// Set the image to display on the window. 263 | pub fn set_image(&mut self, name: impl Into, image: &ImageView) { 264 | let image = self.context().make_gpu_image(name, image); 265 | self.window_mut().image = Some(image); 266 | self.window_mut().uniforms.mark_dirty(true); 267 | self.window_mut().window.request_redraw(); 268 | } 269 | 270 | /// Add an overlay to the window. 271 | /// 272 | /// Overlays are drawn on top of the image in the order that they are first added. 273 | /// If you wish to change the order of existing overlays, you must remove and re-add the overlays. 274 | /// 275 | /// If the window already has an overlay with the same name, 276 | /// the overlay is overwritten and the `initially_visible` argument is ignored. 277 | /// If you want to change the visibility of the overlay, you can call [`set_overlay_visible()`][Self::set_overlay_visible]. 278 | /// If you do so before your function returns, it is guaranteed to have taken effect before the next redraw. 279 | pub fn set_overlay(&mut self, name: impl Into, image: &ImageView, initially_visible: bool) { 280 | use indexmap::map::Entry; 281 | 282 | let name = name.into(); 283 | let image = self.context().make_gpu_image(name.clone(), image); 284 | match self.window_mut().overlays.entry(name) { 285 | Entry::Occupied(mut entry) => { 286 | entry.get_mut().image = image; 287 | }, 288 | Entry::Vacant(entry) => { 289 | entry.insert(Overlay { 290 | image, 291 | visible: initially_visible, 292 | }); 293 | }, 294 | }; 295 | self.window().window.request_redraw() 296 | } 297 | 298 | /// Remove an overlay from the window. 299 | /// 300 | /// Returns `true` if there was an overlay to remove. 301 | pub fn remove_overlay(&mut self, name: &impl AsRef) -> bool { 302 | let removed = self.window_mut().overlays.shift_remove(name.as_ref()).is_some(); 303 | self.window().window.request_redraw(); 304 | removed 305 | } 306 | 307 | /// Remove all overlays from the window. 308 | pub fn clear_overlays(&mut self) { 309 | self.window_mut().overlays.clear(); 310 | self.window().window.request_redraw() 311 | } 312 | 313 | /// Check if an overlay is visible or not. 314 | pub fn is_overlay_visible(&mut self, name: impl AsRef) -> Result { 315 | Ok(self.window().get_overlay(name)?.visible) 316 | } 317 | 318 | /// Make a specific overlay visible or invisible for this window. 319 | /// 320 | /// The overlay is not removed, but it will not be rendered anymore untill you make it visible again. 321 | pub fn set_overlay_visible(&mut self, name: impl AsRef, visible: bool) -> Result<(), error::UnknownOverlay> { 322 | self.window_mut().get_overlay_mut(name)?.visible = visible; 323 | self.window().window.request_redraw(); 324 | Ok(()) 325 | } 326 | 327 | /// Toggle an overlay between visible and invisible. 328 | pub fn toggle_overlay_visible(&mut self, name: impl AsRef) -> Result<(), error::UnknownOverlay> { 329 | let overlay = self.window_mut().get_overlay_mut(name)?; 330 | overlay.visible = !overlay.visible; 331 | self.window().window.request_redraw(); 332 | Ok(()) 333 | } 334 | 335 | /// Make all overlays visible or invisible for this window. 336 | pub fn set_all_overlays_visible(&mut self, visible: bool) { 337 | for (_name, overlay) in &mut self.window_mut().overlays { 338 | overlay.visible = visible; 339 | } 340 | self.window().window.request_redraw() 341 | } 342 | 343 | /// Add an event handler to the window. 344 | pub fn add_event_handler(&mut self, handler: F) 345 | where 346 | F: 'static + FnMut(WindowHandle, &mut WindowEvent, &mut EventHandlerControlFlow), 347 | { 348 | self.window_mut().event_handlers.push(Box::new(handler)) 349 | } 350 | 351 | /// Get the image transformation. 352 | /// 353 | /// The image transformation is applied to the image and all overlays in virtual window space. 354 | /// 355 | /// Virtual window space goes from `(0, 0)` in the top left corner of the window to `(1, 1)` in the bottom right corner. 356 | /// 357 | /// This transformation does not include scaling introduced by the [`Self::preserve_aspect_ratio()`] property. 358 | /// Use [`Self::effective_transform()`] if you need that. 359 | pub fn transform(&self) -> Affine2 { 360 | self.window().user_transform 361 | } 362 | 363 | /// Get the full effective transformation from image space to virtual window space. 364 | /// 365 | /// This transformation maps the image coordinates to virtual window coordinates. 366 | /// Unlike [`Self::transform()`], this function returns a transformation that include the scaling introduced by the [`Self::preserve_aspect_ratio()`] property. 367 | /// This is useful to transform between window coordinates and image coordinates. 368 | /// 369 | /// If no image is set on the window yet, this returns the same transformation as [`Self::transform()`]. 370 | /// 371 | /// Virtual window space goes from `(0, 0)` in the top left corner of the window to `(1, 1)` in the bottom right corner. 372 | /// 373 | /// Note that physical pixel locations must be transformed to virtual window coordinates first. 374 | pub fn effective_transform(&self) -> Affine2 { 375 | self.window().calculate_uniforms().transform 376 | } 377 | 378 | /// Set the image transformation to a value. 379 | /// 380 | /// The image transformation is applied to the image and all overlays in virtual window space. 381 | /// 382 | /// Virtual window space goes from `(0, 0)` in the top left corner of the window to `(1, 1)` in the bottom right corner. 383 | /// 384 | /// This transformation should not include any scaling related to the [`Self::preserve_aspect_ratio()`] property. 385 | pub fn set_transform(&mut self, transform: Affine2) { 386 | self.window_mut().user_transform = transform; 387 | self.window_mut().uniforms.mark_dirty(true); 388 | self.window().window.request_redraw(); 389 | } 390 | 391 | /// Pre-apply a transformation to the existing image transformation. 392 | /// 393 | /// This is equivalent to: 394 | /// ``` 395 | /// # use show_image::{glam::Affine2, WindowHandle}; 396 | /// # fn foo(window: &mut WindowHandle, transform: Affine2) { 397 | /// window.set_transform(transform * window.transform()) 398 | /// # } 399 | /// ``` 400 | /// 401 | /// See [`Self::set_transform`] for more information about the image transformation. 402 | pub fn pre_apply_transform(&mut self, transform: Affine2) { 403 | self.set_transform(transform * self.transform()); 404 | } 405 | 406 | /// Post-apply a transformation to the existing image transformation. 407 | /// 408 | /// This is equivalent to: 409 | /// ``` 410 | /// # use show_image::{glam::Affine2, WindowHandle}; 411 | /// # fn foo(window: &mut WindowHandle, transform: Affine2) { 412 | /// window.set_transform(window.transform() * transform) 413 | /// # } 414 | /// ``` 415 | /// 416 | /// See [`Self::set_transform`] for more information about the image transformation. 417 | pub fn post_apply_transform(&mut self, transform: Affine2) { 418 | self.set_transform(self.transform() * transform) 419 | } 420 | } 421 | 422 | /// Options for creating a new window. 423 | #[derive(Debug, Clone)] 424 | pub struct WindowOptions { 425 | /// Preserve the aspect ratio of the image when scaling. 426 | pub preserve_aspect_ratio: bool, 427 | 428 | /// The background color for the window. 429 | /// 430 | /// This is used to color areas without image data if `preserve_aspect_ratio` is true. 431 | pub background_color: Color, 432 | 433 | /// Create the window hidden. 434 | /// 435 | /// The window can manually be made visible at a later time. 436 | pub start_hidden: bool, 437 | 438 | /// The initial size of the window in pixel. 439 | /// 440 | /// This may be ignored by some window managers. 441 | pub size: Option<[u32; 2]>, 442 | 443 | /// If true allow the window to be resized. 444 | /// 445 | /// This may be ignored by some window managers. 446 | pub resizable: bool, 447 | 448 | /// Make the window borderless. 449 | /// 450 | /// This may be ignored by some window managers. 451 | pub borderless: bool, 452 | 453 | /// Make the window fullscreen. 454 | /// 455 | /// This may be ignored by some window managers. 456 | pub fullscreen: bool, 457 | 458 | /// If true, draw overlays on the image. 459 | /// 460 | /// Defaults to true. 461 | pub overlays_visible: bool, 462 | 463 | /// If true, enable default mouse based controls for panning and zooming the image. 464 | /// 465 | /// Defaults to true. 466 | pub default_controls: bool, 467 | } 468 | 469 | impl Default for WindowOptions { 470 | fn default() -> Self { 471 | Self::new() 472 | } 473 | } 474 | 475 | impl WindowOptions { 476 | /// Create new window options with default values. 477 | pub fn new() -> Self { 478 | Self { 479 | preserve_aspect_ratio: true, 480 | background_color: Color::black(), 481 | start_hidden: false, 482 | size: None, 483 | resizable: true, 484 | borderless: false, 485 | fullscreen: false, 486 | overlays_visible: true, 487 | default_controls: true, 488 | } 489 | } 490 | 491 | /// Preserve the aspect ratio of displayed images, or not. 492 | /// 493 | /// This function consumes and returns `self` to allow daisy chaining. 494 | pub fn set_preserve_aspect_ratio(mut self, preserve_aspect_ratio: bool) -> Self { 495 | self.preserve_aspect_ratio = preserve_aspect_ratio; 496 | self 497 | } 498 | 499 | /// Set the background color of the window. 500 | /// 501 | /// This function consumes and returns `self` to allow daisy chaining. 502 | pub fn set_background_color(mut self, background_color: Color) -> Self { 503 | self.background_color = background_color; 504 | self 505 | } 506 | 507 | /// Start the window hidden. 508 | /// 509 | /// This function consumes and returns `self` to allow daisy chaining. 510 | pub fn set_start_hidden(mut self, start_hidden: bool) -> Self { 511 | self.start_hidden = start_hidden; 512 | self 513 | } 514 | 515 | /// Set the initial size of the window. 516 | /// 517 | /// Pass [`None`] to clear a previously set value, 518 | /// which will let the window manager choose the initial size. 519 | /// 520 | /// This property may be ignored by some window managers. 521 | /// 522 | /// This function consumes and returns `self` to allow daisy chaining. 523 | pub fn set_size(mut self, size: impl Into>) -> Self { 524 | self.size = size.into(); 525 | self 526 | } 527 | 528 | /// Make the window resizable or not. 529 | /// 530 | /// This property may be ignored by some window managers. 531 | /// 532 | /// This function consumes and returns `self` to allow daisy chaining. 533 | pub fn set_resizable(mut self, resizable: bool) -> Self { 534 | self.resizable = resizable; 535 | self 536 | } 537 | 538 | /// Make the window borderless or not. 539 | /// 540 | /// This function consumes and returns `self` to allow daisy chaining. 541 | pub fn set_borderless(mut self, borderless: bool) -> Self { 542 | self.borderless = borderless; 543 | self 544 | } 545 | 546 | /// Make the window fullscreen or not. 547 | /// 548 | /// This function consumes and returns `self` to allow daisy chaining. 549 | pub fn set_fullscreen(mut self, fullscreen: bool) -> Self { 550 | self.fullscreen = fullscreen; 551 | self 552 | } 553 | 554 | /// Set whether or not overlays should be drawn on the window. 555 | pub fn set_show_overlays(mut self, overlays_visible: bool) -> Self { 556 | self.overlays_visible = overlays_visible; 557 | self 558 | } 559 | 560 | /// Set whether or not default mouse controls for panning and zooming the image should be added. 561 | pub fn set_default_controls(mut self, default_controls: bool) -> Self { 562 | self.default_controls = default_controls; 563 | self 564 | } 565 | } 566 | 567 | impl Window { 568 | /// Get the window ID. 569 | pub fn id(&self) -> WindowId { 570 | self.window.id() 571 | } 572 | 573 | /// Make the window visible or invisible. 574 | pub fn set_visible(&mut self, visible: bool) { 575 | self.window.set_visible(visible); 576 | } 577 | 578 | /// Recalculate the uniforms for the render pipeline from the window state. 579 | pub fn calculate_uniforms(&self) -> WindowUniforms { 580 | if let Some(image) = &self.image { 581 | let image_size = image.info().size.as_vec2(); 582 | if !self.preserve_aspect_ratio { 583 | WindowUniforms::stretch(image_size) 584 | .pre_apply_transform(self.user_transform) 585 | } else { 586 | let window_size = glam::UVec2::new(self.window.inner_size().width, self.window.inner_size().height).as_vec2(); 587 | WindowUniforms::fit(window_size, image_size) 588 | .pre_apply_transform(self.user_transform) 589 | } 590 | } else { 591 | WindowUniforms { 592 | transform: self.user_transform, 593 | image_size: Vec2::new(0.0, 0.0), 594 | } 595 | } 596 | } 597 | 598 | fn get_overlay(&self, name: impl AsRef) -> Result<&Overlay, error::UnknownOverlay> { 599 | let name = name.as_ref(); 600 | self.overlays.get(name) 601 | .ok_or_else(|| error::UnknownOverlay { name: name.into() }) 602 | } 603 | 604 | fn get_overlay_mut(&mut self, name: impl AsRef) -> Result<&mut Overlay, error::UnknownOverlay> { 605 | let name = name.as_ref(); 606 | self.overlays.get_mut(name) 607 | .ok_or_else(|| error::UnknownOverlay { name: name.into() }) 608 | } 609 | } 610 | 611 | /// The window specific uniforms for the render pipeline. 612 | #[derive(Debug, Copy, Clone)] 613 | pub(crate) struct WindowUniforms { 614 | /// The transformation applied to the image. 615 | /// 616 | /// With the identity transform, the image is stretched to the inner window size, 617 | /// without preserving the aspect ratio. 618 | pub transform: Affine2, 619 | 620 | /// The size of the image in pixels. 621 | pub image_size: Vec2, 622 | } 623 | 624 | impl WindowUniforms { 625 | pub fn no_image() -> Self { 626 | Self::stretch(Vec2::new(0.0, 0.0)) 627 | } 628 | 629 | pub fn stretch(image_size: Vec2) -> Self { 630 | Self { 631 | transform: Affine2::IDENTITY, 632 | image_size, 633 | } 634 | } 635 | 636 | pub fn fit(window_size: Vec2, image_size: Vec2) -> Self { 637 | let ratios = image_size / window_size; 638 | 639 | let w; 640 | let h; 641 | if ratios.x >= ratios.y { 642 | w = 1.0; 643 | h = ratios.y / ratios.x; 644 | } else { 645 | w = ratios.x / ratios.y; 646 | h = 1.0; 647 | } 648 | 649 | let transform = Affine2::from_scale_angle_translation(Vec2::new(w, h), 0.0, 0.5 * Vec2::new(1.0 - w, 1.0 - h)); 650 | Self { 651 | transform, 652 | image_size, 653 | } 654 | } 655 | 656 | /// Pre-apply a transformation. 657 | pub fn pre_apply_transform(mut self, transform: Affine2) -> Self { 658 | self.transform = transform * self.transform; 659 | self 660 | } 661 | } 662 | 663 | #[repr(C, align(8))] 664 | #[derive(Debug, Copy, Clone)] 665 | struct Vec2A8 { 666 | pub x: f32, 667 | pub y: f32, 668 | } 669 | 670 | #[repr(C, align(16))] 671 | #[derive(Debug, Copy, Clone)] 672 | struct Vec3A16 { 673 | pub x: f32, 674 | pub y: f32, 675 | pub z: f32, 676 | } 677 | 678 | #[repr(C)] 679 | #[derive(Debug, Copy, Clone)] 680 | struct Mat3x3 { 681 | pub cols: [Vec3A16; 3] 682 | } 683 | 684 | impl Vec2A8 { 685 | pub const fn new(x: f32, y: f32) -> Self { 686 | Self { x, y } 687 | } 688 | } 689 | 690 | impl Vec3A16 { 691 | pub const fn new(x: f32, y: f32, z: f32) -> Self { 692 | Self { x, y, z } 693 | } 694 | } 695 | 696 | impl Mat3x3 { 697 | pub const fn new(col0: Vec3A16, col1: Vec3A16, col2: Vec3A16) -> Self { 698 | Self { 699 | cols: [col0, col1, col2], 700 | } 701 | } 702 | } 703 | 704 | impl From for Vec2A8 { 705 | fn from(other: Vec2) -> Self { 706 | Self::new(other.x, other.y) 707 | } 708 | } 709 | 710 | impl From for Vec3A16 { 711 | fn from(other: Vec3) -> Self { 712 | Self::new(other.x, other.y, other.z) 713 | } 714 | } 715 | 716 | impl From for Mat3x3 { 717 | fn from(other: Affine2) -> Self { 718 | let x_axis = other.matrix2.x_axis; 719 | let y_axis = other.matrix2.y_axis; 720 | let z_axis = other.translation; 721 | Self::new( 722 | Vec3A16::new(x_axis.x, x_axis.y, 0.0), 723 | Vec3A16::new(y_axis.x, y_axis.y, 0.0), 724 | Vec3A16::new(z_axis.x, z_axis.y, 1.0), 725 | ) 726 | } 727 | } 728 | 729 | /// Window specific unfiforms, layout compatible with glsl std140. 730 | #[repr(C)] 731 | #[derive(Debug, Copy, Clone)] 732 | pub struct WindowUniformsStd140 { 733 | image_size: Vec2A8, 734 | transform: Mat3x3, 735 | } 736 | 737 | unsafe impl crate::backend::util::ToStd140 for WindowUniforms { 738 | type Output = WindowUniformsStd140; 739 | 740 | fn to_std140(&self) -> Self::Output { 741 | Self::Output { 742 | image_size: self.image_size.into(), 743 | transform: self.transform.into(), 744 | } 745 | } 746 | } 747 | 748 | /// Event handler that implements the default controls. 749 | pub(super) fn default_controls_handler(mut window: WindowHandle, event: &mut crate::event::WindowEvent, _control_flow: &mut crate::event::EventHandlerControlFlow) { 750 | match event { 751 | WindowEvent::MouseWheel(event) => { 752 | let delta = match event.delta { 753 | winit::event::MouseScrollDelta::LineDelta(_x, y) => y, 754 | winit::event::MouseScrollDelta::PixelDelta(delta) => delta.y as f32 / 20.0, 755 | }; 756 | let scale = 1.1f32.powf(delta); 757 | 758 | let origin = event.position 759 | .map(|pos| pos / window.inner_size().as_vec2()) 760 | .unwrap_or_else(|| glam::Vec2::new(0.5, 0.5)); 761 | let transform = glam::Affine2::from_scale_angle_translation(glam::Vec2::splat(scale), 0.0, origin - scale * origin); 762 | window.pre_apply_transform(transform); 763 | }, 764 | WindowEvent::MouseMove(event) => { 765 | if event.buttons.is_pressed(crate::event::MouseButton::Left) { 766 | let translation = (event.position - event.prev_position) / window.inner_size().as_vec2(); 767 | window.pre_apply_transform(Affine2::from_translation(translation)); 768 | } 769 | }, 770 | _ => (), 771 | } 772 | } 773 | -------------------------------------------------------------------------------- /src/background_thread.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::AtomicBool; 2 | use std::sync::atomic::Ordering; 3 | use std::sync::Arc; 4 | 5 | pub struct BackgroundThread { 6 | done: Arc, 7 | handle: std::thread::JoinHandle, 8 | } 9 | 10 | impl BackgroundThread { 11 | pub fn new(f: F) -> Self 12 | where 13 | F: FnOnce() -> T, 14 | F: Send + 'static, 15 | T: Send + 'static, 16 | { 17 | let done = Arc::new(AtomicBool::new(false)); 18 | let handle = std::thread::spawn({ 19 | let done = done.clone(); 20 | move || { 21 | let result = f(); 22 | done.store(true, Ordering::Release); 23 | result 24 | } 25 | }); 26 | 27 | Self { done, handle } 28 | } 29 | 30 | pub fn is_done(&self) -> bool { 31 | self.done.load(Ordering::Acquire) 32 | } 33 | 34 | pub fn join(self) -> std::thread::Result { 35 | self.handle.join() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types for the crate. 2 | 3 | use crate::WindowId; 4 | 5 | /// An error that can occur while creating a new window. 6 | #[derive(Debug)] 7 | pub enum CreateWindowError { 8 | /// The underlying call to `winit` reported an error. 9 | Winit(winit::error::OsError), 10 | 11 | /// Failed to get a suitable GPU device. 12 | GetDevice(GetDeviceError), 13 | 14 | /// Failed to create a surface for drawing. 15 | CreateSurface(wgpu::CreateSurfaceError), 16 | } 17 | 18 | /// An error that can occur while interpreting image data. 19 | #[derive(Debug, Clone, Eq, PartialEq)] 20 | pub enum ImageDataError { 21 | /// The image data is not in a supported format. 22 | UnsupportedImageFormat(UnsupportedImageFormat), 23 | 24 | /// An other error occured. 25 | Other(String), 26 | } 27 | 28 | /// An error indicating that the image data is not in a supported format. 29 | #[derive(Debug, Clone, Eq, PartialEq)] 30 | pub struct UnsupportedImageFormat { 31 | /// The unsupported format. 32 | pub format: String, 33 | } 34 | 35 | /// The window ID is not valid. 36 | #[derive(Debug, Clone, Eq, PartialEq)] 37 | pub struct InvalidWindowId { 38 | /// The invalid window ID. 39 | pub window_id: WindowId, 40 | } 41 | 42 | /// An error that can occur when setting the image of a window. 43 | #[derive(Debug, Clone, Eq, PartialEq)] 44 | pub enum SetImageError { 45 | /// The window ID is invalid. 46 | InvalidWindowId(InvalidWindowId), 47 | 48 | /// The image data is not supported. 49 | ImageDataError(ImageDataError), 50 | } 51 | 52 | /// The specified overlay was not found on the window. 53 | #[derive(Debug, Clone, Eq, PartialEq)] 54 | pub struct UnknownOverlay { 55 | /// The name of the overlay. 56 | pub name: String, 57 | } 58 | 59 | /// An error occured trying to find a usable graphics device. 60 | #[derive(Debug, Clone, Eq, PartialEq)] 61 | pub enum GetDeviceError { 62 | /// No suitable video adapter was found. 63 | NoSuitableAdapterFound(NoSuitableAdapterFound), 64 | 65 | /// No suitable graphics device was found. 66 | NoSuitableDeviceFound(wgpu::RequestDeviceError), 67 | } 68 | 69 | /// No suitable video adapter was found. 70 | #[derive(Debug, Clone, Eq, PartialEq)] 71 | pub struct NoSuitableAdapterFound; 72 | 73 | /// An error occured trying to save an image. 74 | #[derive(Debug)] 75 | pub enum SaveImageError { 76 | /// An I/O error occured. 77 | IoError(std::io::Error), 78 | 79 | /// An error occured encoding the PNG image. 80 | #[cfg(feature = "png")] 81 | PngError(png::EncodingError), 82 | } 83 | 84 | impl From for CreateWindowError { 85 | fn from(other: winit::error::OsError) -> Self { 86 | Self::Winit(other) 87 | } 88 | } 89 | 90 | impl From for CreateWindowError { 91 | fn from(other: GetDeviceError) -> Self { 92 | Self::GetDevice(other) 93 | } 94 | } 95 | 96 | impl From for CreateWindowError { 97 | fn from(other: wgpu::CreateSurfaceError) -> Self { 98 | Self::CreateSurface(other) 99 | } 100 | } 101 | 102 | impl From for SetImageError { 103 | fn from(other: ImageDataError) -> Self { 104 | Self::ImageDataError(other) 105 | } 106 | } 107 | 108 | impl From for SetImageError { 109 | fn from(other: InvalidWindowId) -> Self { 110 | Self::InvalidWindowId(other) 111 | } 112 | } 113 | 114 | impl From for ImageDataError { 115 | fn from(other: UnsupportedImageFormat) -> Self { 116 | Self::UnsupportedImageFormat(other) 117 | } 118 | } 119 | 120 | impl From for ImageDataError { 121 | fn from(other: String) -> Self { 122 | Self::Other(other) 123 | } 124 | } 125 | 126 | impl<'a> From<&'a str> for ImageDataError { 127 | fn from(other: &'a str) -> Self { 128 | Self::Other(other.to_string()) 129 | } 130 | } 131 | 132 | impl From for GetDeviceError { 133 | fn from(other: NoSuitableAdapterFound) -> Self { 134 | Self::NoSuitableAdapterFound(other) 135 | } 136 | } 137 | 138 | impl From for GetDeviceError { 139 | fn from(other: wgpu::RequestDeviceError) -> Self { 140 | Self::NoSuitableDeviceFound(other) 141 | } 142 | } 143 | 144 | impl From for SaveImageError { 145 | fn from(other: std::io::Error) -> Self { 146 | Self::IoError(other) 147 | } 148 | } 149 | 150 | #[cfg(feature = "png")] 151 | impl From for SaveImageError { 152 | fn from(other: png::EncodingError) -> Self { 153 | match other { 154 | png::EncodingError::IoError(e) => Self::IoError(e), 155 | e => Self::PngError(e), 156 | } 157 | } 158 | } 159 | 160 | impl std::error::Error for CreateWindowError {} 161 | impl std::error::Error for ImageDataError {} 162 | impl std::error::Error for UnsupportedImageFormat {} 163 | impl std::error::Error for InvalidWindowId {} 164 | impl std::error::Error for SetImageError {} 165 | impl std::error::Error for UnknownOverlay {} 166 | impl std::error::Error for GetDeviceError {} 167 | impl std::error::Error for NoSuitableAdapterFound {} 168 | impl std::error::Error for SaveImageError {} 169 | 170 | impl std::fmt::Display for CreateWindowError { 171 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 172 | match self { 173 | Self::Winit(e) => write!(f, "{}", e), 174 | Self::GetDevice(e) => write!(f, "{}", e), 175 | Self::CreateSurface(e) => write!(f, "{}", e), 176 | } 177 | } 178 | } 179 | 180 | impl std::fmt::Display for ImageDataError { 181 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 182 | match self { 183 | Self::UnsupportedImageFormat(e) => write!(f, "{}", e), 184 | Self::Other(e) => write!(f, "{}", e), 185 | } 186 | } 187 | } 188 | 189 | impl std::fmt::Display for UnsupportedImageFormat { 190 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 191 | write!(f, "unsupported image format: {}", self.format) 192 | } 193 | } 194 | 195 | impl std::fmt::Display for InvalidWindowId { 196 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 197 | write!(f, "invalid window ID: {:?}", self.window_id) 198 | } 199 | } 200 | 201 | impl std::fmt::Display for SetImageError { 202 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 203 | match self { 204 | Self::InvalidWindowId(e) => write!(f, "{}", e), 205 | Self::ImageDataError(e) => write!(f, "{}", e), 206 | } 207 | } 208 | } 209 | 210 | impl std::fmt::Display for UnknownOverlay { 211 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 212 | write!(f, "unknown overlay: {}", self.name) 213 | } 214 | } 215 | 216 | impl std::fmt::Display for GetDeviceError { 217 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 218 | match self { 219 | Self::NoSuitableAdapterFound(e) => write!(f, "{}", e), 220 | Self::NoSuitableDeviceFound(e) => write!(f, "{}", e), 221 | } 222 | } 223 | } 224 | 225 | impl std::fmt::Display for NoSuitableAdapterFound { 226 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 227 | write!(f, "no suitable graphics adapter found") 228 | } 229 | } 230 | 231 | impl std::fmt::Display for SaveImageError { 232 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 233 | match self { 234 | Self::IoError(e) => write!(f, "{}", e), 235 | #[cfg(feature = "png")] 236 | Self::PngError(e) => write!(f, "{}", e), 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/event/device.rs: -------------------------------------------------------------------------------- 1 | use super::AxisId; 2 | use super::ButtonId; 3 | use super::DeviceId; 4 | use super::ElementState; 5 | use super::KeyboardInput; 6 | use super::MouseScrollDelta; 7 | 8 | /// Raw hardware events that are not associated with any particular window. 9 | /// 10 | /// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person game controls. 11 | /// Many physical actions, such as mouse movement, can produce both device and window events. 12 | /// Because window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs may not match. 13 | /// 14 | /// Note that these events are delivered regardless of input focus. 15 | #[derive(Debug, Clone)] 16 | pub enum DeviceEvent { 17 | /// A new device was added. 18 | Added(DeviceAddedEvent), 19 | 20 | /// A device was removed. 21 | Removed(DeviceRemovedEvent), 22 | 23 | /// Change in physical position of a pointing device. 24 | MouseMotion(DeviceMouseMotionEvent), 25 | 26 | /// The scroll-wheel of a mouse was moved. 27 | MouseWheel(DeviceMouseWheelEvent), 28 | 29 | /// Motion on some analog axis. 30 | Motion(DeviceMotionEvent), 31 | 32 | /// A button on a device was pressed or released. 33 | Button(DeviceButtonEvent), 34 | 35 | /// A device generated keyboard input. 36 | KeyboardInput(DeviceKeyboardInputEvent), 37 | 38 | /// A device generated text input. 39 | TextInput(DeviceTextInputEvent), 40 | } 41 | 42 | #[derive(Debug, Clone)] 43 | /// A new device was added. 44 | pub struct DeviceAddedEvent { 45 | /// The ID of the device. 46 | pub device_id: DeviceId, 47 | } 48 | 49 | /// A device was removed. 50 | #[derive(Debug, Clone)] 51 | pub struct DeviceRemovedEvent { 52 | /// The ID of the device. 53 | pub device_id: DeviceId, 54 | } 55 | 56 | /// The physical position of a pointing device was moved. 57 | /// 58 | /// This represents raw, unfiltered physical motion. 59 | /// Not to be confused with [`WindowMouseMoveEvent`][super::WindowMouseMoveEvent]. 60 | #[derive(Debug, Clone)] 61 | pub struct DeviceMouseMotionEvent { 62 | /// The ID of the device. 63 | pub device_id: DeviceId, 64 | 65 | /// The relative motion. 66 | pub delta: glam::Vec2, 67 | } 68 | 69 | /// The scroll-wheel of a mouse was moved. 70 | #[derive(Debug, Clone)] 71 | pub struct DeviceMouseWheelEvent { 72 | /// The ID of the device. 73 | pub device_id: DeviceId, 74 | 75 | /// The scroll delta. 76 | pub delta: MouseScrollDelta, 77 | } 78 | 79 | /// An analog axis of a device was moved. 80 | /// 81 | /// This event will be reported for all arbitrary input devices that winit supports on this platform, including mouse devices. 82 | /// If the device is a mouse device then this will be reported alongside the [`DeviceMouseMotionEvent`]. 83 | #[derive(Debug, Clone)] 84 | pub struct DeviceMotionEvent { 85 | /// The ID of the device. 86 | pub device_id: DeviceId, 87 | 88 | /// The axis that was moved. 89 | pub axis: AxisId, 90 | 91 | /// The value by which the axis was moved. 92 | pub value: f64, 93 | } 94 | 95 | /// A button on a device was pressed or released. 96 | #[derive(Debug, Clone)] 97 | pub struct DeviceButtonEvent { 98 | /// The ID of the device. 99 | pub device_id: DeviceId, 100 | 101 | /// The button that was pressed or released. 102 | pub button: ButtonId, 103 | 104 | /// The new state of the button (pressed or released). 105 | pub state: ElementState, 106 | } 107 | 108 | /// A device generated keyboard input. 109 | #[derive(Debug, Clone)] 110 | pub struct DeviceKeyboardInputEvent { 111 | /// The ID of the device. 112 | pub device_id: DeviceId, 113 | 114 | /// The keyboard input. 115 | pub input: KeyboardInput, 116 | } 117 | 118 | /// A device generated text input. 119 | #[derive(Debug, Clone)] 120 | pub struct DeviceTextInputEvent { 121 | /// The ID of the device. 122 | pub device_id: DeviceId, 123 | 124 | /// The unicode codepoint that was generated. 125 | pub codepoint: char, 126 | } 127 | 128 | impl_from_variant!(DeviceEvent::Added(DeviceAddedEvent)); 129 | impl_from_variant!(DeviceEvent::Removed(DeviceRemovedEvent)); 130 | impl_from_variant!(DeviceEvent::MouseMotion(DeviceMouseMotionEvent)); 131 | impl_from_variant!(DeviceEvent::MouseWheel(DeviceMouseWheelEvent)); 132 | impl_from_variant!(DeviceEvent::Motion(DeviceMotionEvent)); 133 | impl_from_variant!(DeviceEvent::Button(DeviceButtonEvent)); 134 | impl_from_variant!(DeviceEvent::KeyboardInput(DeviceKeyboardInputEvent)); 135 | impl_from_variant!(DeviceEvent::TextInput(DeviceTextInputEvent)); 136 | -------------------------------------------------------------------------------- /src/event/mod.rs: -------------------------------------------------------------------------------- 1 | //! Event types. 2 | 3 | pub use device::*; 4 | pub use window::*; 5 | 6 | pub use winit::event::AxisId; 7 | pub use winit::event::ButtonId; 8 | pub use winit::event::DeviceId; 9 | pub use winit::event::Force; 10 | pub use winit::event::ModifiersState; 11 | pub use winit::event::MouseScrollDelta; 12 | pub use winit::event::ScanCode; 13 | pub use winit::event::StartCause; 14 | pub use winit::event::Touch; 15 | pub use winit::event::TouchPhase; 16 | pub use winit::event::VirtualKeyCode; 17 | 18 | macro_rules! impl_from_variant { 19 | ($for:ident::$variant:ident($from:ty)) => { 20 | impl From<$from> for $for { 21 | fn from(other: $from) -> Self { 22 | Self::$variant(other) 23 | } 24 | } 25 | }; 26 | } 27 | 28 | mod device; 29 | mod window; 30 | 31 | /// Control flow properties for event handlers. 32 | /// 33 | /// Instances of this struct are passed to event handlers 34 | /// to allow them to remove themselves and to stop event propagation. 35 | #[derive(Debug, Default, Clone)] 36 | pub struct EventHandlerControlFlow { 37 | /// Remove the event handler after it returned. 38 | pub remove_handler: bool, 39 | 40 | /// Stop propagation of the event to other event handlers. 41 | pub stop_propagation: bool, 42 | } 43 | 44 | /// Global event. 45 | /// 46 | /// This also includes window events for all windows. 47 | #[derive(Debug, Clone)] 48 | pub enum Event { 49 | /// New events are available for processing. 50 | /// 51 | /// This indicates the start of a new event-processing cycle. 52 | NewEvents, 53 | 54 | /// A window event. 55 | WindowEvent(WindowEvent), 56 | 57 | /// A device event. 58 | DeviceEvent(DeviceEvent), 59 | 60 | /// The application has been suspended. 61 | Suspended, 62 | 63 | /// The application has been resumed. 64 | Resumed, 65 | 66 | /// All input events have been processed and redraw processing is about to begin. 67 | MainEventsCleared, 68 | 69 | /// All open redraw requests have been processed. 70 | RedrawEventsCleared, 71 | 72 | /// All windows were closed. 73 | /// 74 | /// This event can be received multiple times if you open a new window after all windows were closed. 75 | AllWindowsClosed, 76 | } 77 | 78 | impl_from_variant!(Event::WindowEvent(WindowEvent)); 79 | impl_from_variant!(Event::DeviceEvent(DeviceEvent)); 80 | 81 | /// Keyboard input. 82 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] 83 | pub struct KeyboardInput { 84 | /// Scan code of the physical key. 85 | /// 86 | /// This should not change if the user adjusts the host's keyboard map. 87 | /// Use when the physical location of the key is more important than the key's host GUI semantics, such as for movement controls in a first-person game. 88 | pub scan_code: ScanCode, 89 | 90 | /// Virtual key code indentifying the semantic meaning of the key. 91 | /// 92 | /// Use this when the semantics of the key are more important than the physical location of the key, such as when implementing appropriate behavior for "page up". 93 | pub key_code: Option, 94 | 95 | /// State of the key (pressed or released). 96 | pub state: ElementState, 97 | 98 | /// Keyboard modifiers that were active at the time of the event. 99 | pub modifiers: ModifiersState, 100 | } 101 | 102 | /// OS theme (light or dark). 103 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] 104 | pub enum Theme { 105 | /// The theme is a light theme. 106 | Light, 107 | 108 | /// The theme is a dark theme. 109 | Dark, 110 | } 111 | 112 | impl Theme { 113 | /// Check if the theme is light. 114 | pub fn is_light(self) -> bool { 115 | self == Self::Light 116 | } 117 | 118 | /// Check if the theme is dark. 119 | pub fn is_dark(self) -> bool { 120 | self == Self::Dark 121 | } 122 | } 123 | 124 | /// State of a button or key. 125 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] 126 | pub enum ElementState { 127 | /// The button or key is pressed. 128 | Pressed, 129 | 130 | /// The button or key is released. 131 | Released, 132 | } 133 | 134 | impl ElementState { 135 | /// Check if the button or key is pressed. 136 | pub fn is_pressed(self) -> bool { 137 | self == Self::Pressed 138 | } 139 | 140 | /// Check if the button or key is released. 141 | pub fn is_released(self) -> bool { 142 | self == Self::Released 143 | } 144 | } 145 | 146 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] 147 | /// A mouse button. 148 | pub enum MouseButton { 149 | /// The left mouse button. 150 | Left, 151 | 152 | /// The right mouse button. 153 | Right, 154 | 155 | /// The middle mouse button (usually triggered by pressing the scroll wheel). 156 | Middle, 157 | 158 | /// An other mouse button identified by index. 159 | Other(u16), 160 | } 161 | 162 | impl MouseButton { 163 | /// Check if the button is the left mouse button. 164 | pub fn is_left(self) -> bool { 165 | self == Self::Left 166 | } 167 | 168 | /// Check if the button is the right mouse button. 169 | pub fn is_right(self) -> bool { 170 | self == Self::Right 171 | } 172 | 173 | /// Check if the button is the middle mouse button. 174 | pub fn is_middle(self) -> bool { 175 | self == Self::Middle 176 | } 177 | 178 | /// Check if the button is a specific other button. 179 | pub fn is_other(self, other: u16) -> bool { 180 | self == Self::Other(other) 181 | } 182 | } 183 | 184 | /// The state of all mouse buttons. 185 | #[derive(Debug, Clone, Default)] 186 | pub struct MouseButtonState { 187 | /// The set of pressed buttons. 188 | buttons: std::collections::BTreeSet, 189 | } 190 | 191 | impl MouseButtonState { 192 | /// Check if a button is pressed. 193 | pub fn is_pressed(&self, button: MouseButton) -> bool { 194 | self.buttons.get(&button).is_some() 195 | } 196 | 197 | /// Iterate over all pressed buttons. 198 | pub fn iter_pressed(&self) -> impl Iterator + '_ { 199 | self.buttons.iter().copied() 200 | } 201 | 202 | /// Mark a button as pressed or unpressed. 203 | pub fn set_pressed(&mut self, button: MouseButton, pressed: bool) { 204 | if pressed { 205 | self.buttons.insert(button); 206 | } else { 207 | self.buttons.remove(&button); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/event/window.rs: -------------------------------------------------------------------------------- 1 | use super::AxisId; 2 | use super::DeviceId; 3 | use super::ElementState; 4 | use super::KeyboardInput; 5 | use super::ModifiersState; 6 | use super::MouseButton; 7 | use super::MouseButtonState; 8 | use super::MouseScrollDelta; 9 | use super::Theme; 10 | use super::Touch; 11 | use super::TouchPhase; 12 | use crate::WindowId; 13 | 14 | use std::path::PathBuf; 15 | 16 | /// Window event. 17 | #[derive(Debug, Clone)] 18 | pub enum WindowEvent { 19 | /// A redraw was requested by the OS or application code. 20 | RedrawRequested(WindowRedrawRequestedEvent), 21 | 22 | /// A window was resized. 23 | Resized(WindowResizedEvent), 24 | 25 | /// A window was moved. 26 | Moved(WindowMovedEvent), 27 | 28 | /// A window was closed. 29 | CloseRequested(WindowCloseRequestedEvent), 30 | 31 | /// A window was destroyed. 32 | Destroyed(WindowDestroyedEvent), 33 | 34 | /// A file was dropped on a window. 35 | DroppedFile(WindowDroppedFileEvent), 36 | 37 | /// A file is being hovered over a window. 38 | HoveredFile(WindowHoveredFileEvent), 39 | 40 | /// A file that was being hovered over a window was canceled.. 41 | HoveredFileCancelled(WindowHoveredFileCancelledEvent), 42 | 43 | /// A window gained input focus. 44 | FocusGained(WindowFocusGainedEvent), 45 | 46 | /// A window lost input focus. 47 | FocusLost(WindowFocusLostEvent), 48 | 49 | /// A window received keyboard input. 50 | KeyboardInput(WindowKeyboardInputEvent), 51 | 52 | /// A window received text input. 53 | TextInput(WindowTextInputEvent), 54 | 55 | /// The mouse cursor entered a window. 56 | MouseEnter(WindowMouseEnterEvent), 57 | 58 | /// The mouse cursor left a window. 59 | MouseLeave(WindowMouseLeaveEvent), 60 | 61 | /// The mouse cursor was moved on a window. 62 | MouseMove(WindowMouseMoveEvent), 63 | 64 | /// A mouse button was pressed or released on a window. 65 | MouseButton(WindowMouseButtonEvent), 66 | 67 | /// A window received mouse wheel input. 68 | MouseWheel(WindowMouseWheelEvent), 69 | 70 | /// A window received axis motion input. 71 | AxisMotion(WindowAxisMotionEvent), 72 | 73 | /// A window received touchpad pressure input. 74 | TouchpadPressure(WindowTouchpadPressureEvent), 75 | 76 | /// A window received a touchpad magnify event. 77 | /// 78 | /// On supported platforms, the event is triggered moving two fingers towards or away from each-other on the touchpad. 79 | /// 80 | /// *Platform specific:* Only available on macOS. 81 | TouchpadMagnify(WindowTouchpadMagnifyEvent), 82 | 83 | /// A window received a touchpad rotate event. 84 | /// 85 | /// On supported platforms, the event is triggered putting two fingers on the touchpad and rotating them. 86 | /// 87 | /// *Platform specific:* Only available on macOS. 88 | TouchpadRotate(WindowTouchpadRotateEvent), 89 | 90 | /// A window received touch input. 91 | Touch(WindowTouchEvent), 92 | 93 | /// The scale factor between logical and physical pixels for a window changed. 94 | ScaleFactorChanged(WindowScaleFactorChangedEvent), 95 | 96 | /// The theme for a window changed. 97 | ThemeChanged(WindowThemeChangedEvent), 98 | } 99 | 100 | impl WindowEvent { 101 | /// Get the window ID of the event. 102 | pub fn window_id(&self) -> WindowId { 103 | match self { 104 | Self::RedrawRequested(x) => x.window_id, 105 | Self::Resized(x) => x.window_id, 106 | Self::Moved(x) => x.window_id, 107 | Self::CloseRequested(x) => x.window_id, 108 | Self::Destroyed(x) => x.window_id, 109 | Self::DroppedFile(x) => x.window_id, 110 | Self::HoveredFile(x) => x.window_id, 111 | Self::HoveredFileCancelled(x) => x.window_id, 112 | Self::FocusGained(x) => x.window_id, 113 | Self::FocusLost(x) => x.window_id, 114 | Self::KeyboardInput(x) => x.window_id, 115 | Self::TextInput(x) => x.window_id, 116 | Self::MouseEnter(x) => x.window_id, 117 | Self::MouseLeave(x) => x.window_id, 118 | Self::MouseMove(x) => x.window_id, 119 | Self::MouseButton(x) => x.window_id, 120 | Self::MouseWheel(x) => x.window_id, 121 | Self::AxisMotion(x) => x.window_id, 122 | Self::TouchpadPressure(x) => x.window_id, 123 | Self::TouchpadMagnify(x) => x.window_id, 124 | Self::TouchpadRotate(x) => x.window_id, 125 | Self::Touch(x) => x.window_id, 126 | Self::ScaleFactorChanged(x) => x.window_id, 127 | Self::ThemeChanged(x) => x.window_id, 128 | } 129 | } 130 | } 131 | 132 | /// A redraw was requested by the OS or application code. 133 | #[derive(Debug, Clone)] 134 | pub struct WindowRedrawRequestedEvent { 135 | /// The ID of the window. 136 | pub window_id: WindowId, 137 | } 138 | 139 | /// A window was resized. 140 | #[derive(Debug, Clone)] 141 | pub struct WindowResizedEvent { 142 | /// The ID of the window. 143 | pub window_id: WindowId, 144 | 145 | /// The new size of the window in physical pixels. 146 | pub size: glam::UVec2, 147 | } 148 | 149 | /// A window was moved. 150 | #[derive(Debug, Clone)] 151 | pub struct WindowMovedEvent { 152 | /// The ID of the window. 153 | pub window_id: WindowId, 154 | 155 | /// The new position of the window in physical pixels. 156 | pub position: glam::IVec2, 157 | } 158 | 159 | /// A window was closed. 160 | #[derive(Debug, Clone)] 161 | pub struct WindowCloseRequestedEvent { 162 | /// The ID of the window. 163 | pub window_id: WindowId, 164 | } 165 | 166 | /// A window was destroyed. 167 | #[derive(Debug, Clone)] 168 | pub struct WindowDestroyedEvent { 169 | /// The ID of the window. 170 | pub window_id: WindowId, 171 | } 172 | 173 | /// A file was dropped on a window. 174 | #[derive(Debug, Clone)] 175 | pub struct WindowDroppedFileEvent { 176 | /// The ID of the window. 177 | pub window_id: WindowId, 178 | 179 | /// The path of the file. 180 | pub file: PathBuf, 181 | } 182 | 183 | /// A file is being hovered over a window. 184 | #[derive(Debug, Clone)] 185 | pub struct WindowHoveredFileEvent { 186 | /// The ID of the window. 187 | pub window_id: WindowId, 188 | 189 | /// The path of the file. 190 | pub file: PathBuf, 191 | } 192 | 193 | /// A file that was being hovered over a window was canceled.. 194 | #[derive(Debug, Clone)] 195 | pub struct WindowHoveredFileCancelledEvent { 196 | /// The ID of the window. 197 | pub window_id: WindowId, 198 | } 199 | 200 | /// A window gained input focus. 201 | #[derive(Debug, Clone)] 202 | pub struct WindowFocusGainedEvent { 203 | /// The ID of the window. 204 | pub window_id: WindowId, 205 | } 206 | 207 | /// A window lost input focus. 208 | #[derive(Debug, Clone)] 209 | pub struct WindowFocusLostEvent { 210 | /// The ID of the window. 211 | pub window_id: WindowId, 212 | } 213 | 214 | /// A window received keyboard input. 215 | #[derive(Debug, Clone)] 216 | pub struct WindowKeyboardInputEvent { 217 | /// The ID of the window. 218 | pub window_id: WindowId, 219 | 220 | /// The device that generated the input. 221 | pub device_id: DeviceId, 222 | 223 | /// The received input. 224 | pub input: KeyboardInput, 225 | 226 | /// Flag to indicate if the input is synthetic. 227 | /// 228 | /// Some synthetic events may be generated to report changes in keyboard state while the window did not have input focus. 229 | /// This flag allows you to distinguish such events. 230 | pub is_synthetic: bool, 231 | } 232 | 233 | /// A window received text input. 234 | #[derive(Debug, Clone)] 235 | pub struct WindowTextInputEvent { 236 | /// The ID of the window. 237 | pub window_id: WindowId, 238 | 239 | /// The unicode codepoint representing the input. 240 | pub character: char, 241 | } 242 | 243 | /// The mouse cursor entered the window area. 244 | #[derive(Debug, Clone)] 245 | pub struct WindowMouseEnterEvent { 246 | /// The ID of the window. 247 | pub window_id: WindowId, 248 | 249 | /// The device that generated the input. 250 | pub device_id: DeviceId, 251 | 252 | /// The pressed state of all mouse buttons. 253 | pub buttons: MouseButtonState, 254 | } 255 | 256 | /// The mouse cursor left the window area. 257 | #[derive(Debug, Clone)] 258 | pub struct WindowMouseLeaveEvent { 259 | /// The ID of the window. 260 | pub window_id: WindowId, 261 | 262 | /// The device that generated the input. 263 | pub device_id: DeviceId, 264 | 265 | /// The pressed state of all mouse buttons. 266 | pub buttons: MouseButtonState, 267 | } 268 | 269 | /// The mouse cursor was moved on a window. 270 | #[derive(Debug, Clone)] 271 | pub struct WindowMouseMoveEvent { 272 | /// The ID of the window. 273 | pub window_id: WindowId, 274 | 275 | /// The device that generated the input. 276 | pub device_id: DeviceId, 277 | 278 | /// The new position of the cursor in physical pixels, relative to the top-left corner of the window. 279 | pub position: glam::Vec2, 280 | 281 | /// The position of the mouse cursor before the last movement. 282 | pub prev_position: glam::Vec2, 283 | 284 | /// The pressed state of all mouse buttons. 285 | pub buttons: MouseButtonState, 286 | 287 | /// The state of the keyboard modifiers at the time of the event. 288 | pub modifiers: ModifiersState, 289 | } 290 | 291 | /// A window received mouse input. 292 | #[derive(Debug, Clone)] 293 | pub struct WindowMouseButtonEvent { 294 | /// The ID of the window. 295 | pub window_id: WindowId, 296 | 297 | /// The device that generated the input. 298 | pub device_id: DeviceId, 299 | 300 | /// The mouse button that was pressed. 301 | pub button: MouseButton, 302 | 303 | /// The new state of the mouse button. 304 | pub state: ElementState, 305 | 306 | /// The current position of the mouse cursor inside the window. 307 | pub position: glam::Vec2, 308 | 309 | /// The position of the mouse cursor before the last movement. 310 | pub prev_position: glam::Vec2, 311 | 312 | /// The pressed state of all mouse buttons. 313 | pub buttons: MouseButtonState, 314 | 315 | /// The state of the keyboard modifiers at the time of the event. 316 | pub modifiers: ModifiersState, 317 | } 318 | 319 | /// A window received mouse wheel input. 320 | #[derive(Debug, Clone)] 321 | pub struct WindowMouseWheelEvent { 322 | /// The ID of the window. 323 | pub window_id: WindowId, 324 | 325 | /// The device that generated the input. 326 | pub device_id: DeviceId, 327 | 328 | /// The scroll delta of the mouse wheel. 329 | pub delta: MouseScrollDelta, 330 | 331 | /// The touch-screen input state. 332 | pub phase: TouchPhase, 333 | 334 | /// The current position of the mouse cursor inside the window. 335 | pub position: Option, 336 | 337 | /// The pressed state of all mouse buttons. 338 | pub buttons: MouseButtonState, 339 | 340 | /// The state of the keyboard modifiers at the time of the event. 341 | pub modifiers: ModifiersState, 342 | } 343 | 344 | /// A window received axis motion input. 345 | #[derive(Debug, Clone)] 346 | pub struct WindowAxisMotionEvent { 347 | /// The ID of the window. 348 | pub window_id: WindowId, 349 | 350 | /// The device that generated the input. 351 | pub device_id: DeviceId, 352 | 353 | /// The axis that as moved. 354 | pub axis: AxisId, 355 | 356 | /// The value by which the axis moved. 357 | pub value: f64, 358 | } 359 | 360 | /// A window received touchpad pressure input. 361 | #[derive(Debug, Clone)] 362 | pub struct WindowTouchpadPressureEvent { 363 | /// The ID of the window. 364 | pub window_id: WindowId, 365 | 366 | /// The device that generated the input. 367 | pub device_id: DeviceId, 368 | 369 | /// The pressure on the touch pad, in the range 0 to 1. 370 | pub pressure: f32, 371 | 372 | /// The click level of the touch pad. 373 | pub stage: i64, 374 | } 375 | 376 | /// A window received touchpad magnify input. 377 | #[derive(Debug, Clone)] 378 | pub struct WindowTouchpadMagnifyEvent { 379 | /// The ID of the window. 380 | pub window_id: WindowId, 381 | 382 | /// The device that generated the input. 383 | pub device_id: DeviceId, 384 | 385 | /// The scaling to be applied. 386 | /// 387 | /// Values between 0.0 and 1.0 indicate zooming out. 388 | /// Values above 1.0 indicate zooming in. 389 | pub scale: f64, 390 | 391 | /// The touch phase for the event. 392 | pub phase: TouchPhase, 393 | } 394 | 395 | /// A window received touchpad rotate input. 396 | #[derive(Debug, Clone)] 397 | pub struct WindowTouchpadRotateEvent { 398 | /// The ID of the window. 399 | pub window_id: WindowId, 400 | 401 | /// The device that generated the input. 402 | pub device_id: DeviceId, 403 | 404 | /// The rotation angle in radians. 405 | /// 406 | /// A positive rotation is counter clockwise. 407 | pub angle_radians: f64, 408 | 409 | /// The touch phase for the event. 410 | pub phase: TouchPhase, 411 | } 412 | 413 | /// A window received touch input. 414 | #[derive(Debug, Clone)] 415 | pub struct WindowTouchEvent { 416 | /// The ID of the window. 417 | pub window_id: WindowId, 418 | 419 | /// The touch input. 420 | pub touch: Touch, 421 | } 422 | 423 | /// The scale factor between logical and physical pixels for a window changed. 424 | #[derive(Debug, Clone)] 425 | pub struct WindowScaleFactorChangedEvent { 426 | /// The ID of the window. 427 | pub window_id: WindowId, 428 | 429 | /// The new scale factor as physical pixels per logical pixel. 430 | pub scale_factor: f64, 431 | } 432 | 433 | /// The theme for a window changed. 434 | #[derive(Debug, Clone)] 435 | pub struct WindowThemeChangedEvent { 436 | /// The ID of the window. 437 | pub window_id: WindowId, 438 | 439 | /// The new theme of the window. 440 | pub theme: Theme, 441 | } 442 | 443 | impl_from_variant!(WindowEvent::RedrawRequested(WindowRedrawRequestedEvent)); 444 | impl_from_variant!(WindowEvent::Resized(WindowResizedEvent)); 445 | impl_from_variant!(WindowEvent::Moved(WindowMovedEvent)); 446 | impl_from_variant!(WindowEvent::CloseRequested(WindowCloseRequestedEvent)); 447 | impl_from_variant!(WindowEvent::Destroyed(WindowDestroyedEvent)); 448 | impl_from_variant!(WindowEvent::DroppedFile(WindowDroppedFileEvent)); 449 | impl_from_variant!(WindowEvent::HoveredFile(WindowHoveredFileEvent)); 450 | impl_from_variant!(WindowEvent::HoveredFileCancelled(WindowHoveredFileCancelledEvent)); 451 | impl_from_variant!(WindowEvent::FocusGained(WindowFocusGainedEvent)); 452 | impl_from_variant!(WindowEvent::FocusLost(WindowFocusLostEvent)); 453 | impl_from_variant!(WindowEvent::KeyboardInput(WindowKeyboardInputEvent)); 454 | impl_from_variant!(WindowEvent::TextInput(WindowTextInputEvent)); 455 | impl_from_variant!(WindowEvent::MouseEnter(WindowMouseEnterEvent)); 456 | impl_from_variant!(WindowEvent::MouseLeave(WindowMouseLeaveEvent)); 457 | impl_from_variant!(WindowEvent::MouseMove(WindowMouseMoveEvent)); 458 | impl_from_variant!(WindowEvent::MouseButton(WindowMouseButtonEvent)); 459 | impl_from_variant!(WindowEvent::MouseWheel(WindowMouseWheelEvent)); 460 | impl_from_variant!(WindowEvent::AxisMotion(WindowAxisMotionEvent)); 461 | impl_from_variant!(WindowEvent::TouchpadPressure(WindowTouchpadPressureEvent)); 462 | impl_from_variant!(WindowEvent::TouchpadMagnify(WindowTouchpadMagnifyEvent)); 463 | impl_from_variant!(WindowEvent::TouchpadRotate(WindowTouchpadRotateEvent)); 464 | impl_from_variant!(WindowEvent::Touch(WindowTouchEvent)); 465 | impl_from_variant!(WindowEvent::ScaleFactorChanged(WindowScaleFactorChangedEvent)); 466 | impl_from_variant!(WindowEvent::ThemeChanged(WindowThemeChangedEvent)); 467 | -------------------------------------------------------------------------------- /src/features/image.rs: -------------------------------------------------------------------------------- 1 | //! Support for the [`image`][::image] crate. 2 | 3 | use std::ops::Deref; 4 | 5 | use crate::error::ImageDataError; 6 | use crate::Alpha; 7 | use crate::AsImageView; 8 | use crate::BoxImage; 9 | use crate::Image; 10 | use crate::ImageInfo; 11 | use crate::ImageView; 12 | use crate::PixelFormat; 13 | use crate::error::UnsupportedImageFormat; 14 | 15 | impl AsImageView for image::DynamicImage { 16 | fn as_image_view(&self) -> Result { 17 | let info = dynamic_image_info(self)?; 18 | let data = dynamic_image_as_bytes(self); 19 | Ok(ImageView::new(info, data)) 20 | } 21 | } 22 | 23 | impl AsImageView for &'_ image::DynamicImage { 24 | fn as_image_view(&self) -> Result { 25 | (*self).as_image_view() 26 | } 27 | } 28 | 29 | impl From for Image { 30 | fn from(other: image::DynamicImage) -> Self { 31 | let info = match dynamic_image_info(&other) { 32 | Ok(x) => x, 33 | Err(e) => return Self::Invalid(e), 34 | }; 35 | let data = dynamic_image_into_bytes(other); 36 | BoxImage::new(info, data).into() 37 | } 38 | } 39 | 40 | impl AsImageView for image::ImageBuffer 41 | where 42 | P: image::Pixel + image::PixelWithColorType, 43 | Container: Deref, 44 | { 45 | fn as_image_view(&self) -> Result { 46 | let info = info(self)?; 47 | let data = as_bytes(self); 48 | Ok(ImageView::new(info, data)) 49 | } 50 | } 51 | 52 | impl AsImageView for &'_ image::ImageBuffer 53 | where 54 | P: image::Pixel + image::PixelWithColorType, 55 | Container: Deref, 56 | { 57 | fn as_image_view(&self) -> Result { 58 | (*self).as_image_view() 59 | } 60 | } 61 | 62 | impl From> for Image 63 | where 64 | P: image::Pixel + image::PixelWithColorType, 65 | Container: Deref, 66 | { 67 | fn from(other: image::ImageBuffer) -> Self { 68 | let info = match info(&other) { 69 | Ok(x) => x, 70 | Err(e) => return Self::Invalid(e), 71 | }; 72 | let data = into_bytes(other); 73 | BoxImage::new(info, data).into() 74 | } 75 | } 76 | 77 | /// Consume an [`image::ImageBuffer`] and return the pixel data as boxed slice. 78 | fn into_bytes(buffer: image::ImageBuffer) -> Box<[u8]> 79 | where 80 | P: image::Pixel + image::PixelWithColorType, 81 | Container: Deref, 82 | { 83 | // TODO: Specialize this for Vec to avoid copying when 84 | // https://github.com/rust-lang/rust/issues/31844 lands in stable. 85 | Box::from(buffer.into_raw().deref()) 86 | } 87 | 88 | fn dynamic_image_into_bytes(image: image::DynamicImage) -> Box<[u8]> { 89 | match image { 90 | image::DynamicImage::ImageLuma8(x) => into_bytes(x), 91 | image::DynamicImage::ImageLumaA8(x) => into_bytes(x), 92 | image::DynamicImage::ImageLuma16(_) => panic!("unsupported pixel format: Luma16"), 93 | image::DynamicImage::ImageLumaA16(_) => panic!("unsupported pixel format: LumaA16"), 94 | image::DynamicImage::ImageRgb8(x) => into_bytes(x), 95 | image::DynamicImage::ImageRgba8(x) => into_bytes(x), 96 | image::DynamicImage::ImageRgb16(_) => panic!("unsupported pixel format: Rgb16"), 97 | image::DynamicImage::ImageRgba16(_) => panic!("unsupported pixel format: Rgba16"), 98 | image::DynamicImage::ImageRgb32F(_) => panic!("unsupported pixel format: Rgb32F"), 99 | image::DynamicImage::ImageRgba32F(_) => panic!("unsupported pixel format: Rgba32F"), 100 | x => panic!("unsupported pixel format: {:?}", x), 101 | } 102 | } 103 | 104 | /// Get the pixel data of an [`image::ImageBuffer`] to as a byte slice. 105 | fn as_bytes(buffer: &image::ImageBuffer) -> &[u8] 106 | where 107 | P: image::Pixel + image::PixelWithColorType, 108 | Container: Deref, 109 | { 110 | buffer 111 | } 112 | 113 | fn dynamic_image_as_bytes(image: &image::DynamicImage) -> &[u8] { 114 | match image { 115 | image::DynamicImage::ImageLuma8(x) => as_bytes(x), 116 | image::DynamicImage::ImageLumaA8(x) => as_bytes(x), 117 | image::DynamicImage::ImageLuma16(_) => panic!("unsupported pixel format: Luma16"), 118 | image::DynamicImage::ImageLumaA16(_) => panic!("unsupported pixel format: LumaA16"), 119 | image::DynamicImage::ImageRgb8(x) => as_bytes(x), 120 | image::DynamicImage::ImageRgba8(x) => as_bytes(x), 121 | image::DynamicImage::ImageRgb16(_) => panic!("unsupported pixel format: Rgb16"), 122 | image::DynamicImage::ImageRgba16(_) => panic!("unsupported pixel format: Rgba16"), 123 | image::DynamicImage::ImageRgb32F(_) => panic!("unsupported pixel format: Rgb32F"), 124 | image::DynamicImage::ImageRgba32F(_) => panic!("unsupported pixel format: Rgba32F"), 125 | x => panic!("unsupported pixel format: {:?}", x), 126 | } 127 | } 128 | 129 | /// Extract the [`ImageInfo`] from an [`image::ImageBuffer`]. 130 | fn info(image: &image::ImageBuffer) -> Result 131 | where 132 | P: image::Pixel + image::PixelWithColorType, 133 | C: std::ops::Deref, 134 | { 135 | Ok(ImageInfo { 136 | pixel_format: pixel_format::

()?, 137 | size: glam::UVec2::new(image.width(), image.height()), 138 | stride: glam::UVec2::new( 139 | image.sample_layout().width_stride as u32, 140 | image.sample_layout().height_stride as u32, 141 | ), 142 | }) 143 | } 144 | 145 | fn dynamic_image_info(image: &image::DynamicImage) -> Result { 146 | match image { 147 | image::DynamicImage::ImageLuma8(x) => info(x), 148 | image::DynamicImage::ImageLumaA8(x) => info(x), 149 | image::DynamicImage::ImageRgb8(x) => info(x), 150 | image::DynamicImage::ImageRgba8(x) => info(x), 151 | x => Err(UnsupportedImageFormat { format: format!("{:?}", x) }.into()), 152 | } 153 | } 154 | 155 | /// Extract the PixelFormat from an [`image::Pixel`]. 156 | fn pixel_format() -> Result { 157 | match P::COLOR_TYPE { 158 | image::ExtendedColorType::L8 => Ok(PixelFormat::Mono8), 159 | image::ExtendedColorType::La8 => Ok(PixelFormat::MonoAlpha8(Alpha::Unpremultiplied)), 160 | image::ExtendedColorType::Rgb8 => Ok(PixelFormat::Rgb8), 161 | image::ExtendedColorType::Rgba8 => Ok(PixelFormat::Rgba8(Alpha::Unpremultiplied)), 162 | x => Err(UnsupportedImageFormat { format: format!("{:?}", x) }.into()), 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/features/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "image")] 2 | #[cfg_attr(feature = "nightly", doc(cfg(feature = "image")))] 3 | pub mod image; 4 | 5 | #[cfg(feature = "raqote")] 6 | #[cfg_attr(feature = "nightly", doc(cfg(feature = "raqote")))] 7 | pub mod raqote; 8 | 9 | #[cfg(feature = "tch")] 10 | #[cfg_attr(feature = "nightly", doc(cfg(feature = "tch")))] 11 | pub mod tch; 12 | -------------------------------------------------------------------------------- /src/features/raqote.rs: -------------------------------------------------------------------------------- 1 | //! Support for the [`raqote`][::raqote] crate. 2 | 3 | use crate::error::ImageDataError; 4 | use crate::BoxImage; 5 | use crate::Image; 6 | use crate::ImageInfo; 7 | 8 | impl From for Image { 9 | fn from(other: raqote::DrawTarget) -> Self { 10 | let info = match draw_target_info(&other) { 11 | Ok(x) => x, 12 | Err(e) => return Image::Invalid(e), 13 | }; 14 | 15 | let length = other.get_data_u8().len(); 16 | let buffer = Box::into_raw(other.into_vec().into_boxed_slice()) as *mut u8; 17 | let buffer = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(buffer, length)) }; 18 | 19 | BoxImage::new(info, buffer).into() 20 | } 21 | } 22 | 23 | impl From<&raqote::DrawTarget> for Image { 24 | fn from(other: &raqote::DrawTarget) -> Self { 25 | let info = match draw_target_info(other) { 26 | Ok(x) => x, 27 | Err(e) => return Image::Invalid(e), 28 | }; 29 | 30 | let buffer = Box::from(other.get_data_u8()); 31 | 32 | BoxImage::new(info, buffer).into() 33 | } 34 | } 35 | 36 | impl From> for Image { 37 | fn from(other: raqote::Image) -> Self { 38 | let info = match image_info(&other) { 39 | Ok(x) => x, 40 | Err(e) => return Image::Invalid(e), 41 | }; 42 | 43 | let buffer = other.data.as_ptr() as *const u8; 44 | let buffer = unsafe { Box::from(std::slice::from_raw_parts(buffer, other.data.len() * 4)) }; 45 | 46 | BoxImage::new(info, buffer).into() 47 | } 48 | } 49 | 50 | fn draw_target_info(draw_target: &raqote::DrawTarget) -> Result { 51 | if draw_target.width() < 0 || draw_target.height() < 0 { 52 | Err(format!("DrawTarget has negative size: [{}, {}]", draw_target.width(), draw_target.height()).into()) 53 | } else { 54 | Ok(ImageInfo::bgra8_premultiplied( 55 | draw_target.width() as u32, 56 | draw_target.height() as u32, 57 | )) 58 | } 59 | } 60 | 61 | fn image_info(&image: &raqote::Image) -> Result { 62 | if image.width < 0 || image.height < 0 { 63 | Err(format!("DrawTarget has negative size: [{}, {}]", image.width, image.height).into()) 64 | } else { 65 | Ok(ImageInfo::bgra8_premultiplied(image.width as u32, image.height as u32)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/features/tch.rs: -------------------------------------------------------------------------------- 1 | //! Support for the [`tch`][::tch] crate. 2 | //! 3 | //! This module adds support for displaying [`tch::Tensor`] as images. 4 | //! The main interface is provided by an extension trait [`TensorAsImage`], 5 | //! which allows you to wrap a tensor in a [`TensorImage`]. 6 | //! The wrapper struct adds some required meta-data for interpreting the tensor data as an image. 7 | //! 8 | //! The meta-data has to be supplied by the user, or it can be guessed automatically based on the tensor shape. 9 | //! When guessing, you do need to specify if you want to interpret multi-channel tensors as RGB or BGR. 10 | //! An extension trait [`TensorAsImage`] is provided to construct the wrapper with the proper meta-data. 11 | //! 12 | //! It is not always possible to interpret a tensor as the requested image format, 13 | //! so all function in the extension trait return a [`Result`]. 14 | //! The [`Into`] trait is implemented for [`TensorImage`] and for [`Result`]`<`[`TensorImage`]`, `[`ImageDataError`]`>`, 15 | //! so you can directly pass use the result to so set the image of a window directly. 16 | //! 17 | //! Both planar and interlaced tensors are supported. 18 | //! If you specify the format manually, you must also specify if the tensor contains interlaced or planar data. 19 | //! If you let the library guess, it will try to deduce it automatically based on the tensor shape. 20 | //! 21 | //! # Example 22 | //! ```no_run 23 | //! use show_image::{create_window, WindowOptions}; 24 | //! use show_image::tch::TensorAsImage; 25 | //! 26 | //! let tensor = tch::vision::imagenet::load_image("/path/to/image.png").unwrap(); 27 | //! let window = create_window("image", WindowOptions::default())?; 28 | //! window.set_image("image-001", tensor.as_image_guess_rgb())?; 29 | //! # Result::<(), Box>::Ok(()) 30 | //! ``` 31 | 32 | use crate::error::ImageDataError; 33 | use crate::Alpha; 34 | use crate::BoxImage; 35 | use crate::Image; 36 | use crate::ImageInfo; 37 | use crate::PixelFormat; 38 | 39 | /// Wrapper for [`tch::Tensor`] that implements `Into`. 40 | pub struct TensorImage<'a> { 41 | tensor: &'a tch::Tensor, 42 | info: ImageInfo, 43 | planar: bool, 44 | } 45 | 46 | /// The pixel format of a tensor, or a color format to guess the pixel format. 47 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 48 | pub enum TensorPixelFormat { 49 | /// The tensor has planar pixel data. 50 | Planar(PixelFormat), 51 | 52 | /// The tensor has interlaced pixel data. 53 | Interlaced(PixelFormat), 54 | 55 | /// The library should guess if the pixel data is planar or interlaced. 56 | Guess(ColorFormat), 57 | } 58 | 59 | /// A preferred color format for guessing the pixel format of a tensor. 60 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 61 | pub enum ColorFormat { 62 | /// Interpret 3 or 4 channel tensors as RGB or RGBA. 63 | Rgb, 64 | 65 | /// Interpret 3 or 4 channel tensors as BGR or BGRA. 66 | Bgr, 67 | } 68 | 69 | /// Extension trait to allow displaying tensors as image. 70 | /// 71 | /// The tensor data will always be copied. 72 | /// Additionally, the data will be converted to 8 bit integers, 73 | /// and planar data will be converted to interlaced data. 74 | /// 75 | /// The original tensor is unaffected, but the conversion can be expensive. 76 | /// If you also need to convert the tensor, consider doing so before displaying it. 77 | #[allow(clippy::needless_lifetimes)] 78 | pub trait TensorAsImage { 79 | /// Wrap the tensor in a [`TensorImage`] that implements `Into`. 80 | /// 81 | /// This function requires you to specify the pixel format of the tensor, 82 | /// or a preferred color format to have the library guess based on the tensor shape. 83 | /// 84 | /// See the other functions in the trait for easier shorthands. 85 | fn as_image<'a>(&'a self, pixel_format: TensorPixelFormat) -> Result, ImageDataError>; 86 | 87 | /// Wrap the tensor with a known pixel format in a [`TensorImage`], assuming it holds interlaced pixel data. 88 | fn as_interlaced<'a>(&'a self, pixel_format: PixelFormat) -> Result, ImageDataError> { 89 | self.as_image(TensorPixelFormat::Interlaced(pixel_format)) 90 | } 91 | 92 | /// Wrap the tensor with a known pixel format in a [`TensorImage`], assuming it holds planar pixel data. 93 | fn as_planar<'a>(&'a self, pixel_format: PixelFormat) -> Result, ImageDataError> { 94 | self.as_image(TensorPixelFormat::Planar(pixel_format)) 95 | } 96 | 97 | /// Wrap the tensor in a [`TensorImage`]. 98 | /// 99 | /// The pixel format of the tensor will be guessed based on the shape. 100 | /// The `color_format` argument determines if tensors with 3 or 4 channels are interpreted as RGB or BGR. 101 | fn as_image_guess<'a>(&'a self, color_format: ColorFormat) -> Result, ImageDataError> { 102 | self.as_image(TensorPixelFormat::Guess(color_format)) 103 | } 104 | 105 | /// Wrap the tensor in a [`TensorImage`]. 106 | /// 107 | /// The pixel format of the tensor will be guessed based on the shape. 108 | /// Tensors with 3 or 4 channels will be interpreted as RGB. 109 | fn as_image_guess_rgb<'a>(&'a self) -> Result, ImageDataError> { 110 | self.as_image_guess(ColorFormat::Rgb) 111 | } 112 | 113 | /// Wrap the tensor in a [`TensorImage`]. 114 | /// 115 | /// The pixel format of the tensor will be guessed based on the shape. 116 | /// Tensors with 3 or 4 channels will be interpreted as BGR. 117 | fn as_image_guess_bgr<'a>(&'a self) -> Result, ImageDataError> { 118 | self.as_image_guess(ColorFormat::Bgr) 119 | } 120 | 121 | /// Wrap the tensor in a [`TensorImage`], assuming it holds monochrome data. 122 | fn as_mono8<'a>(&'a self) -> Result, ImageDataError> { 123 | self.as_interlaced(PixelFormat::Mono8) 124 | } 125 | 126 | /// Wrap the tensor in a [`TensorImage`], assuming it holds interlaced RGB data. 127 | fn as_interlaced_rgb8<'a>(&'a self) -> Result, ImageDataError> { 128 | self.as_interlaced(PixelFormat::Rgb8) 129 | } 130 | 131 | /// Wrap the tensor in a [`TensorImage`], assuming it holds interlaced RGBA data. 132 | fn as_interlaced_rgba8<'a>(&'a self) -> Result, ImageDataError> { 133 | self.as_interlaced(PixelFormat::Rgba8(Alpha::Unpremultiplied)) 134 | } 135 | 136 | /// Wrap the tensor in a [`TensorImage`], assuming it holds interlaced BGR data. 137 | fn as_interlaced_bgr8<'a>(&'a self) -> Result, ImageDataError> { 138 | self.as_interlaced(PixelFormat::Bgr8) 139 | } 140 | 141 | /// Wrap the tensor in a [`TensorImage`], assuming it holds interlaced BGRA data. 142 | fn as_interlaced_bgra8<'a>(&'a self) -> Result, ImageDataError> { 143 | self.as_interlaced(PixelFormat::Bgra8(Alpha::Unpremultiplied)) 144 | } 145 | 146 | /// Wrap the tensor in a [`TensorImage`], assuming it holds planar RGB data. 147 | fn as_planar_rgb8<'a>(&'a self) -> Result, ImageDataError> { 148 | self.as_planar(PixelFormat::Rgb8) 149 | } 150 | 151 | /// Wrap the tensor in a [`TensorImage`], assuming it holds planar RGBA data. 152 | fn as_planar_rgba8<'a>(&'a self) -> Result, ImageDataError> { 153 | self.as_planar(PixelFormat::Rgba8(Alpha::Unpremultiplied)) 154 | } 155 | 156 | /// Wrap the tensor in a [`TensorImage`], assuming it holds planar BGR data. 157 | fn as_planar_bgr8<'a>(&'a self) -> Result, ImageDataError> { 158 | self.as_planar(PixelFormat::Bgr8) 159 | } 160 | 161 | /// Wrap the tensor in a [`TensorImage`], assuming it holds planar BGRA data. 162 | fn as_planar_bgra8<'a>(&'a self) -> Result, ImageDataError> { 163 | self.as_planar(PixelFormat::Bgra8(Alpha::Unpremultiplied)) 164 | } 165 | } 166 | 167 | impl TensorAsImage for tch::Tensor { 168 | fn as_image(&self, pixel_format: TensorPixelFormat) -> Result { 169 | let (planar, info) = match pixel_format { 170 | TensorPixelFormat::Planar(pixel_format) => (true, tensor_info(self, pixel_format, true)?), 171 | TensorPixelFormat::Interlaced(pixel_format) => (false, tensor_info(self, pixel_format, false)?), 172 | TensorPixelFormat::Guess(color_format) => guess_tensor_info(self, color_format)?, 173 | }; 174 | Ok(TensorImage { 175 | tensor: self, 176 | info, 177 | planar, 178 | }) 179 | } 180 | } 181 | 182 | fn tensor_to_byte_vec(tensor: &tch::Tensor) -> Vec { 183 | let size = tensor.numel() * tensor.kind().elt_size_in_bytes(); 184 | let mut data = vec![0u8; size]; 185 | tensor.copy_data_u8(&mut data, tensor.numel()); 186 | data 187 | } 188 | 189 | impl<'a> From> for Image { 190 | fn from(other: TensorImage<'a>) -> Self { 191 | let data = if other.planar { 192 | tensor_to_byte_vec(&other.tensor.permute([1, 2, 0])) 193 | } else { 194 | tensor_to_byte_vec(other.tensor) 195 | }; 196 | 197 | BoxImage::new(other.info, data.into_boxed_slice()).into() 198 | } 199 | } 200 | 201 | impl<'a> From, ImageDataError>> for Image { 202 | fn from(other: Result, ImageDataError>) -> Self { 203 | match other { 204 | Ok(x) => x.into(), 205 | Err(e) => Image::Invalid(e), 206 | } 207 | } 208 | } 209 | 210 | /// Compute the image info of a tensor, given a known pixel format. 211 | #[allow(clippy::branches_sharing_code)] // Stop lying, clippy. 212 | fn tensor_info(tensor: &tch::Tensor, pixel_format: PixelFormat, planar: bool) -> Result { 213 | let expected_channels = pixel_format.channels(); 214 | let dimensions = tensor.dim(); 215 | 216 | if dimensions == 3 { 217 | let shape = tensor.size3().unwrap(); 218 | if planar { 219 | let (channels, height, width) = shape; 220 | if channels != i64::from(expected_channels) { 221 | Err(format!("expected shape ({}, height, width), found {:?}", expected_channels, shape)) 222 | } else { 223 | Ok(ImageInfo::new(pixel_format, width as u32, height as u32)) 224 | } 225 | } else { 226 | let (height, width, channels) = shape; 227 | if channels != i64::from(expected_channels) { 228 | Err(format!("expected shape (height, width, {}), found {:?}", expected_channels, shape)) 229 | } else { 230 | Ok(ImageInfo::new(pixel_format, width as u32, height as u32)) 231 | } 232 | } 233 | } else if dimensions == 2 && expected_channels == 1 { 234 | let (height, width) = tensor.size2().unwrap(); 235 | Ok(ImageInfo::new(pixel_format, width as u32, height as u32)) 236 | } else { 237 | Err(format!( 238 | "wrong number of dimensions ({}) for format ({:?})", 239 | dimensions, pixel_format 240 | )) 241 | } 242 | } 243 | 244 | /// Guess the image info of a tensor. 245 | fn guess_tensor_info(tensor: &tch::Tensor, color_format: ColorFormat) -> Result<(bool, ImageInfo), String> { 246 | let dimensions = tensor.dim(); 247 | 248 | if dimensions == 2 { 249 | let (height, width) = tensor.size2().unwrap(); 250 | Ok((false, ImageInfo::mono8(width as u32, height as u32))) 251 | } else if dimensions == 3 { 252 | let shape = tensor.size3().unwrap(); 253 | match (shape.0 as u32, shape.1 as u32, shape.2 as u32, color_format) { 254 | (h, w, 1, _) => Ok((false, ImageInfo::mono8(w, h))), 255 | (1, h, w, _) => Ok((false, ImageInfo::mono8(w, h))), // "planar" doesn't do anything here, so call it interlaced 256 | (h, w, 3, ColorFormat::Rgb) => Ok((false, ImageInfo::rgb8(w, h))), 257 | (h, w, 3, ColorFormat::Bgr) => Ok((false, ImageInfo::bgr8(w, h))), 258 | (3, h, w, ColorFormat::Rgb) => Ok((true, ImageInfo::rgb8(w, h))), 259 | (3, h, w, ColorFormat::Bgr) => Ok((true, ImageInfo::bgr8(w, h))), 260 | (h, w, 4, ColorFormat::Rgb) => Ok((false, ImageInfo::rgba8(w, h))), 261 | (h, w, 4, ColorFormat::Bgr) => Ok((false, ImageInfo::bgra8(w, h))), 262 | (4, h, w, ColorFormat::Rgb) => Ok((true, ImageInfo::rgba8(w, h))), 263 | (4, h, w, ColorFormat::Bgr) => Ok((true, ImageInfo::bgra8(w, h))), 264 | _ => Err(format!("unable to guess pixel format for tensor with shape {:?}, expected (height, width) or (height, width, channels) or (channels, height, width) where channels is either 1, 3 or 4", shape)) 265 | } 266 | } else { 267 | Err(format!( 268 | "unable to guess pixel format for tensor with {} dimensions, expected 2 or 3 dimensions", 269 | dimensions 270 | )) 271 | } 272 | } 273 | 274 | #[cfg(test)] 275 | mod test { 276 | use super::*; 277 | use assert2::assert; 278 | 279 | #[test] 280 | fn guess_tensor_info() { 281 | let data = tch::Tensor::from_slice(&(0..120).collect::>()); 282 | 283 | // Guess monochrome from compatible data. 284 | assert!(data.reshape([12, 10, 1]).as_image_guess_bgr().map(|x| x.info) == Ok(ImageInfo::mono8(10, 12))); 285 | assert!(data.reshape([1, 12, 10]).as_image_guess_bgr().map(|x| x.info) == Ok(ImageInfo::mono8(10, 12))); 286 | assert!(data.reshape([12, 10]).as_image_guess_bgr().map(|x| x.info) == Ok(ImageInfo::mono8(10, 12))); 287 | 288 | // Guess RGB[A]/BGR[A] from interlaced data. 289 | assert!(data.reshape([8, 5, 3]).as_image_guess_rgb().map(|x| x.info) == Ok(ImageInfo::rgb8(5, 8))); 290 | assert!(data.reshape([8, 5, 3]).as_image_guess_bgr().map(|x| x.info) == Ok(ImageInfo::bgr8(5, 8))); 291 | assert!(data.reshape([5, 6, 4]).as_image_guess_rgb().map(|x| x.info) == Ok(ImageInfo::rgba8(6, 5))); 292 | assert!(data.reshape([5, 6, 4]).as_image_guess_bgr().map(|x| x.info) == Ok(ImageInfo::bgra8(6, 5))); 293 | 294 | // Guess RGB[A]/BGR[A] from planar data. 295 | assert!(data.reshape([3, 8, 5]).as_image_guess_rgb().map(|x| x.info) == Ok(ImageInfo::rgb8(5, 8))); 296 | assert!(data.reshape([3, 8, 5]).as_image_guess_bgr().map(|x| x.info) == Ok(ImageInfo::bgr8(5, 8))); 297 | assert!(data.reshape([4, 5, 6]).as_image_guess_rgb().map(|x| x.info) == Ok(ImageInfo::rgba8(6, 5))); 298 | assert!(data.reshape([4, 5, 6]).as_image_guess_bgr().map(|x| x.info) == Ok(ImageInfo::bgra8(6, 5))); 299 | 300 | // Fail to guess on other dimensions 301 | assert!(let Err(_) = data.reshape([120]).as_image_guess_rgb().map(|x| x.info)); 302 | assert!(let Err(_) = data.reshape([2, 10, 6]).as_image_guess_rgb().map(|x| x.info)); 303 | assert!(let Err(_) = data.reshape([6, 10, 2]).as_image_guess_rgb().map(|x| x.info)); 304 | assert!(let Err(_) = data.reshape([8, 5, 3, 1]).as_image_guess_rgb().map(|x| x.info)); 305 | assert!(let Err(_) = data.reshape([4, 5, 6, 1]).as_image_guess_rgb().map(|x| x.info)); 306 | } 307 | 308 | #[test] 309 | fn tensor_info_interlaced_with_known_format() { 310 | let data = tch::Tensor::from_slice(&(0..60).collect::>()); 311 | 312 | // Monochrome 313 | assert!(data.reshape([12, 5, 1]).as_mono8().map(|x| x.info) == Ok(ImageInfo::mono8(5, 12))); 314 | assert!(data.reshape([12, 5]).as_mono8().map(|x| x.info) == Ok(ImageInfo::mono8(5, 12))); 315 | assert!(let Err(_) = data.reshape([12, 5, 1, 1]).as_mono8().map(|x| x.info)); 316 | assert!(let Err(_) = data.reshape([6, 5, 2]).as_mono8().map(|x| x.info)); 317 | assert!(let Err(_) = data.reshape([3, 5, 4]).as_mono8().map(|x| x.info)); 318 | assert!(let Err(_) = data.reshape([4, 5, 3]).as_mono8().map(|x| x.info)); 319 | assert!(let Err(_) = data.reshape([60]).as_mono8().map(|x| x.info)); 320 | 321 | // RGB/BGR 322 | assert!(data.reshape([4, 5, 3]).as_interlaced_rgb8().map(|x| x.info) == Ok(ImageInfo::rgb8(5, 4))); 323 | assert!(data.reshape([4, 5, 3]).as_interlaced_bgr8().map(|x| x.info) == Ok(ImageInfo::bgr8(5, 4))); 324 | assert!(let Err(_) = data.reshape([4, 5, 3, 1]).as_interlaced_bgr8().map(|x| x.info)); 325 | assert!(let Err(_) = data.reshape([4, 5, 3, 1]).as_interlaced_bgr8().map(|x| x.info)); 326 | assert!(let Err(_) = data.reshape([3, 5, 4]).as_interlaced_bgr8().map(|x| x.info)); 327 | assert!(let Err(_) = data.reshape([3, 5, 4]).as_interlaced_bgr8().map(|x| x.info)); 328 | assert!(let Err(_) = data.reshape([15, 4]).as_interlaced_rgb8().map(|x| x.info)); 329 | assert!(let Err(_) = data.reshape([15, 4]).as_interlaced_rgb8().map(|x| x.info)); 330 | 331 | // RGBA/BGRA 332 | assert!(data.reshape([3, 5, 4]).as_interlaced_rgba8().map(|x| x.info) == Ok(ImageInfo::rgba8(5, 3))); 333 | assert!(data.reshape([3, 5, 4]).as_interlaced_bgra8().map(|x| x.info) == Ok(ImageInfo::bgra8(5, 3))); 334 | assert!(let Err(_) = data.reshape([3, 5, 4, 1]).as_interlaced_rgba8().map(|x| x.info)); 335 | assert!(let Err(_) = data.reshape([3, 5, 4, 1]).as_interlaced_bgra8().map(|x| x.info)); 336 | assert!(let Err(_) = data.reshape([4, 5, 3]).as_interlaced_rgba8().map(|x| x.info)); 337 | assert!(let Err(_) = data.reshape([4, 5, 3]).as_interlaced_bgra8().map(|x| x.info)); 338 | assert!(let Err(_) = data.reshape([15, 4]).as_interlaced_rgba8().map(|x| x.info)); 339 | assert!(let Err(_) = data.reshape([15, 4]).as_interlaced_bgra8().map(|x| x.info)); 340 | } 341 | 342 | #[test] 343 | fn tensor_info_planar_with_known_format() { 344 | let data = tch::Tensor::from_slice(&(0..60).collect::>()); 345 | 346 | // RGB/BGR 347 | assert!(data.reshape([3, 4, 5]).as_planar_rgb8().map(|x| x.info) == Ok(ImageInfo::rgb8(5, 4))); 348 | assert!(data.reshape([3, 4, 5]).as_planar_bgr8().map(|x| x.info) == Ok(ImageInfo::bgr8(5, 4))); 349 | assert!(let Err(_) = data.reshape([4, 5, 3, 1]).as_planar_bgr8().map(|x| x.info)); 350 | assert!(let Err(_) = data.reshape([4, 5, 3, 1]).as_planar_bgr8().map(|x| x.info)); 351 | assert!(let Err(_) = data.reshape([4, 5, 3]).as_planar_bgr8().map(|x| x.info)); 352 | assert!(let Err(_) = data.reshape([4, 5, 3]).as_planar_bgr8().map(|x| x.info)); 353 | assert!(let Err(_) = data.reshape([15, 4]).as_planar_rgb8().map(|x| x.info)); 354 | assert!(let Err(_) = data.reshape([15, 4]).as_planar_rgb8().map(|x| x.info)); 355 | 356 | // RGBA/BGRA 357 | assert!(data.reshape([4, 3, 5]).as_planar_rgba8().map(|x| x.info) == Ok(ImageInfo::rgba8(5, 3))); 358 | assert!(data.reshape([4, 3, 5]).as_planar_bgra8().map(|x| x.info) == Ok(ImageInfo::bgra8(5, 3))); 359 | assert!(let Err(_) = data.reshape([3, 5, 4, 1]).as_planar_rgba8().map(|x| x.info)); 360 | assert!(let Err(_) = data.reshape([3, 5, 4, 1]).as_planar_bgra8().map(|x| x.info)); 361 | assert!(let Err(_) = data.reshape([3, 5, 4]).as_planar_rgba8().map(|x| x.info)); 362 | assert!(let Err(_) = data.reshape([3, 5, 4]).as_planar_bgra8().map(|x| x.info)); 363 | assert!(let Err(_) = data.reshape([15, 4]).as_planar_rgba8().map(|x| x.info)); 364 | assert!(let Err(_) = data.reshape([15, 4]).as_planar_bgra8().map(|x| x.info)); 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /src/image_info.rs: -------------------------------------------------------------------------------- 1 | /// Information describing the binary data of an image. 2 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 3 | pub struct ImageInfo { 4 | /// The pixel format of the image data. 5 | pub pixel_format: PixelFormat, 6 | 7 | /// The size of the image in pixels 8 | pub size: glam::UVec2, 9 | 10 | /// The stride of the image data in bytes for both X and Y. 11 | pub stride: glam::UVec2, 12 | } 13 | 14 | /// Supported pixel formats. 15 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 16 | pub enum PixelFormat { 17 | /// 8-bit monochrome data. 18 | Mono8, 19 | 20 | /// 8-bit monochrome data with alpha. 21 | MonoAlpha8(Alpha), 22 | 23 | /// Interlaced 8-bit BGR data. 24 | Bgr8, 25 | 26 | /// Interlaced 8-bit BGRA data. 27 | Bgra8(Alpha), 28 | 29 | /// Interlaced 8-bit RGB data. 30 | Rgb8, 31 | 32 | /// Interlaced 8-bit RGBA data. 33 | Rgba8(Alpha), 34 | } 35 | 36 | /// Possible alpha representations. 37 | /// 38 | /// See also: 39 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 40 | pub enum Alpha { 41 | /// The alpha channel is encoded only in the alpha component of the pixel. 42 | Unpremultiplied, 43 | 44 | /// The alpha channel is also premultiplied into the other components of the pixel. 45 | Premultiplied, 46 | } 47 | 48 | impl ImageInfo { 49 | /// Create a new info struct with the given format, width and height. 50 | /// 51 | /// The row stride is automatically calculated based on the image width and pixel format. 52 | /// If you wish to use a different row stride, construct the struct directly. 53 | pub fn new(pixel_format: PixelFormat, width: u32, height: u32) -> Self { 54 | let stride_x = u32::from(pixel_format.bytes_per_pixel()); 55 | let stride_y = stride_x * width; 56 | Self { 57 | pixel_format, 58 | size: glam::UVec2::new(width, height), 59 | stride: glam::UVec2::new(stride_x, stride_y), 60 | } 61 | } 62 | 63 | /// Create a new info struct for an 8-bit monochrome image with the given width and height. 64 | pub fn mono8(width: u32, height: u32) -> Self { 65 | Self::new(PixelFormat::Mono8, width, height) 66 | } 67 | 68 | /// Create a new info struct for an 8-bit monochrome image with with alpha channel and the given width and height. 69 | pub fn mono_alpha8(width: u32, height: u32) -> Self { 70 | Self::new(PixelFormat::MonoAlpha8(Alpha::Unpremultiplied), width, height) 71 | } 72 | 73 | /// Create a new info struct for an 8-bit monochrome image with premultiplied alpha channel and the given width and height. 74 | pub fn mono_alpha8_premultiplied(width: u32, height: u32) -> Self { 75 | Self::new(PixelFormat::MonoAlpha8(Alpha::Premultiplied), width, height) 76 | } 77 | 78 | /// Create a new info struct for an 8-bit BGR image with the given width and height. 79 | pub fn bgr8(width: u32, height: u32) -> Self { 80 | Self::new(PixelFormat::Bgr8, width, height) 81 | } 82 | 83 | /// Create a new info struct for an 8-bit BGRA image with the given width and height. 84 | pub fn bgra8(width: u32, height: u32) -> Self { 85 | Self::new(PixelFormat::Bgra8(Alpha::Unpremultiplied), width, height) 86 | } 87 | 88 | /// Create a new info struct for an 8-bit BGRA image with premultiplied alpha channel and the given width and height. 89 | pub fn bgra8_premultiplied(width: u32, height: u32) -> Self { 90 | Self::new(PixelFormat::Bgra8(Alpha::Premultiplied), width, height) 91 | } 92 | 93 | /// Create a new info struct for an 8-bit RGB image with the given width and height. 94 | pub fn rgb8(width: u32, height: u32) -> Self { 95 | Self::new(PixelFormat::Rgb8, width, height) 96 | } 97 | 98 | /// Create a new info struct for an 8-bit RGBA image with the given width and height. 99 | pub fn rgba8(width: u32, height: u32) -> Self { 100 | Self::new(PixelFormat::Rgba8(Alpha::Unpremultiplied), width, height) 101 | } 102 | 103 | /// Create a new info struct for an 8-bit RGBA image with premultiplied alpha channel and the given width and height. 104 | pub fn rgba8_premultiplied(width: u32, height: u32) -> Self { 105 | Self::new(PixelFormat::Rgba8(Alpha::Premultiplied), width, height) 106 | } 107 | 108 | /// Get the image size in bytes. 109 | pub fn byte_size(self) -> u64 { 110 | if self.stride.y >= self.stride.x { 111 | u64::from(self.stride.y) * u64::from(self.size.y) 112 | } else { 113 | u64::from(self.stride.x) * u64::from(self.size.x) 114 | } 115 | } 116 | } 117 | 118 | impl PixelFormat { 119 | /// Get the number of channels. 120 | pub fn channels(self) -> u8 { 121 | match self { 122 | PixelFormat::Mono8 => 1, 123 | PixelFormat::MonoAlpha8(_) => 1, 124 | PixelFormat::Bgr8 => 3, 125 | PixelFormat::Bgra8(_) => 4, 126 | PixelFormat::Rgb8 => 3, 127 | PixelFormat::Rgba8(_) => 4, 128 | } 129 | } 130 | 131 | /// Get the bytes per channel. 132 | const fn byte_depth(self) -> u8 { 133 | 1 134 | } 135 | 136 | /// Get the bytes per pixel. 137 | pub fn bytes_per_pixel(self) -> u8 { 138 | self.byte_depth() * self.channels() 139 | } 140 | 141 | /// Get the alpha representation of the pixel format. 142 | /// 143 | /// Returns [`None`], if the pixel format has no alpha channel. 144 | pub fn alpha(self) -> Option { 145 | match self { 146 | PixelFormat::Mono8 => None, 147 | PixelFormat::MonoAlpha8(a) => Some(a), 148 | PixelFormat::Bgr8 => None, 149 | PixelFormat::Bgra8(a) => Some(a), 150 | PixelFormat::Rgb8 => None, 151 | PixelFormat::Rgba8(a) => Some(a), 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/image_types.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::error::ImageDataError; 4 | use crate::ImageInfo; 5 | 6 | /// Trait for borrowing image data from a struct. 7 | pub trait AsImageView { 8 | /// Get an image view for the object. 9 | fn as_image_view(&self) -> Result; 10 | } 11 | 12 | /// Get the image info of an object that implements [`AsImageView`]. 13 | pub fn image_info(image: &impl AsImageView) -> Result { 14 | Ok(image.as_image_view()?.info()) 15 | } 16 | 17 | /// Borrowed view of image data, 18 | #[derive(Debug, Copy, Clone)] 19 | pub struct ImageView<'a> { 20 | info: ImageInfo, 21 | data: &'a [u8], 22 | } 23 | 24 | impl<'a> ImageView<'a> { 25 | /// Create a new image view from image information and a data slice. 26 | pub fn new(info: ImageInfo, data: &'a [u8]) -> Self { 27 | Self { info, data } 28 | } 29 | 30 | /// Get the image information. 31 | pub fn info(&self) -> ImageInfo { 32 | self.info 33 | } 34 | 35 | /// Get the image data as byte slice. 36 | pub fn data(&self) -> &[u8] { 37 | self.data 38 | } 39 | } 40 | 41 | impl<'a> AsImageView for ImageView<'a> { 42 | fn as_image_view(&self) -> Result { 43 | Ok(*self) 44 | } 45 | } 46 | 47 | /// Owning image that can be sent to another thread. 48 | /// 49 | /// The image is backed by either a [`Box`] or [`Arc`]. 50 | /// It can either directly own the data or through a [`dyn AsImageView`]. 51 | pub enum Image { 52 | /// An image backed by a `Box<[u8]>`. 53 | Box(BoxImage), 54 | 55 | /// An image backed by an `Arc<[u8]>`. 56 | Arc(ArcImage), 57 | 58 | /// An image backed by a `Box`. 59 | BoxDyn(Box), 60 | 61 | /// An image backed by an `Arc`. 62 | ArcDyn(Arc), 63 | 64 | /// An invalid image that will always fail the conversion to [`ImageView`]. 65 | Invalid(ImageDataError), 66 | } 67 | 68 | impl Clone for Image { 69 | fn clone(&self) -> Self { 70 | match self { 71 | Self::Box(x) => Self::Box(x.clone()), 72 | Self::Arc(x) => Self::Arc(x.clone()), 73 | // We can not clone Box directly, but we can clone the data or the error. 74 | Self::BoxDyn(x) => match x.as_image_view() { 75 | Ok(view) => Self::Box(BoxImage::new(view.info, view.data.into())), 76 | Err(error) => Self::Invalid(error), 77 | }, 78 | Self::ArcDyn(x) => Self::ArcDyn(x.clone()), 79 | Self::Invalid(x) => Self::Invalid(x.clone()), 80 | } 81 | } 82 | } 83 | 84 | impl AsImageView for Box { 85 | fn as_image_view(&self) -> Result { 86 | self.as_ref().as_image_view() 87 | } 88 | } 89 | 90 | impl AsImageView for Arc { 91 | fn as_image_view(&self) -> Result { 92 | self.as_ref().as_image_view() 93 | } 94 | } 95 | 96 | /// Image backed by a `Box<[u8]>`. 97 | #[derive(Debug, Clone)] 98 | pub struct BoxImage { 99 | info: ImageInfo, 100 | data: Box<[u8]>, 101 | } 102 | 103 | /// Image backed by an `Arc<[u8]>`. 104 | #[derive(Debug, Clone)] 105 | pub struct ArcImage { 106 | info: ImageInfo, 107 | data: Arc<[u8]>, 108 | } 109 | 110 | impl Image { 111 | /// Get a non-owning view of the image data. 112 | pub fn as_image_view(&self) -> Result { 113 | match self { 114 | Self::Box(x) => Ok(x.as_view()), 115 | Self::Arc(x) => Ok(x.as_view()), 116 | Self::BoxDyn(x) => x.as_image_view(), 117 | Self::ArcDyn(x) => x.as_image_view(), 118 | Self::Invalid(e) => Err(e.clone()), 119 | } 120 | } 121 | } 122 | 123 | impl AsImageView for Image { 124 | fn as_image_view(&self) -> Result { 125 | self.as_image_view() 126 | } 127 | } 128 | 129 | impl BoxImage { 130 | /// Create a new image from image information and a boxed slice. 131 | pub fn new(info: ImageInfo, data: Box<[u8]>) -> Self { 132 | Self { info, data } 133 | } 134 | 135 | /// Get a non-owning view of the image data. 136 | pub fn as_view(&self) -> ImageView { 137 | ImageView::new(self.info, &self.data) 138 | } 139 | 140 | /// Get the image information. 141 | pub fn info(&self) -> ImageInfo { 142 | self.info 143 | } 144 | 145 | /// Get the image data as byte slice. 146 | pub fn data(&self) -> &[u8] { 147 | &self.data 148 | } 149 | } 150 | 151 | impl AsImageView for BoxImage { 152 | fn as_image_view(&self) -> Result { 153 | Ok(self.as_view()) 154 | } 155 | } 156 | 157 | impl ArcImage { 158 | /// Create a new image from image information and a Arc-wrapped slice. 159 | pub fn new(info: ImageInfo, data: Arc<[u8]>) -> Self { 160 | Self { info, data } 161 | } 162 | 163 | /// Get a non-owning view of the image data. 164 | pub fn as_view(&self) -> ImageView { 165 | ImageView::new(self.info, &self.data) 166 | } 167 | 168 | /// Get the image information. 169 | pub fn info(&self) -> ImageInfo { 170 | self.info 171 | } 172 | 173 | /// Get the image data as byte slice. 174 | pub fn data(&self) -> &[u8] { 175 | &self.data 176 | } 177 | } 178 | 179 | impl AsImageView for ArcImage { 180 | fn as_image_view(&self) -> Result { 181 | Ok(self.as_view()) 182 | } 183 | } 184 | 185 | impl From> for BoxImage { 186 | fn from(other: ImageView) -> Self { 187 | Self { 188 | info: other.info, 189 | data: other.data.into(), 190 | } 191 | } 192 | } 193 | 194 | impl From<&'_ ImageView<'_>> for BoxImage { 195 | fn from(other: &ImageView) -> Self { 196 | Self { 197 | info: other.info, 198 | data: other.data.into(), 199 | } 200 | } 201 | } 202 | 203 | impl From> for ArcImage { 204 | fn from(other: ImageView) -> Self { 205 | Self { 206 | info: other.info, 207 | data: other.data.into(), 208 | } 209 | } 210 | } 211 | 212 | impl From<&'_ ImageView<'_>> for ArcImage { 213 | fn from(other: &ImageView) -> Self { 214 | Self { 215 | info: other.info, 216 | data: other.data.into(), 217 | } 218 | } 219 | } 220 | 221 | impl From> for Image { 222 | fn from(other: ImageView) -> Self { 223 | Self::Box(BoxImage::from(other)) 224 | } 225 | } 226 | 227 | impl From<&'_ ImageView<'_>> for Image { 228 | fn from(other: &ImageView) -> Self { 229 | Self::Box(BoxImage::from(other)) 230 | } 231 | } 232 | 233 | impl From for ArcImage { 234 | fn from(other: BoxImage) -> Self { 235 | Self { 236 | info: other.info, 237 | data: other.data.into(), 238 | } 239 | } 240 | } 241 | 242 | impl From for Image { 243 | fn from(other: BoxImage) -> Self { 244 | Self::Box(other) 245 | } 246 | } 247 | 248 | impl From for Image { 249 | fn from(other: ArcImage) -> Self { 250 | Self::Arc(other) 251 | } 252 | } 253 | 254 | impl From> for Image { 255 | fn from(other: Box) -> Self { 256 | Self::BoxDyn(other) 257 | } 258 | } 259 | 260 | impl From> for Image { 261 | fn from(other: Arc) -> Self { 262 | Self::ArcDyn(other) 263 | } 264 | } 265 | 266 | impl From> for Image 267 | where 268 | T: AsImageView + Send + 'static, 269 | { 270 | fn from(other: Box) -> Self { 271 | Self::BoxDyn(other) 272 | } 273 | } 274 | 275 | impl From> for Image 276 | where 277 | T: AsImageView + Send + Sync + 'static, 278 | { 279 | fn from(other: Arc) -> Self { 280 | Self::ArcDyn(other) 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `show-image` is a library for quickly displaying images. 2 | //! It is intended as a debugging aid for writing image processing code. 3 | //! The library is not intended for making full-featured GUIs, 4 | //! but you can process keyboard events from the created windows. 5 | //! 6 | //! # Supported image types. 7 | //! The library aims to support as many different data types used to represent images. 8 | //! To keep the dependency graph as small as possible, 9 | //! support for third party libraries must be enabled explicitly with feature flags. 10 | //! 11 | //! Currently, the following types are supported: 12 | //! * The [`Image`] and [`ImageView`] types from this crate. 13 | //! * [`image::DynamicImage`][::image::DynamicImage] and [`image::ImageBuffer`][::image::ImageBuffer] (requires the `"image"` feature). 14 | //! * [`tch::Tensor`][::tch::Tensor] (requires the `"tch"` feature). 15 | //! * [`raqote::DrawTarget`][::raqote::DrawTarget] and [`raqote::Image`][::raqote::Image] (requires the `"raqote"` feature). 16 | //! 17 | //! If you think support for a some data type is missing, 18 | //! feel free to send a PR or create an issue on GitHub. 19 | //! 20 | //! # Global context and threading 21 | //! The library uses a global context that runs an event loop. 22 | //! This context must be initialized before any `show-image` functions can be used. 23 | //! Additionally, some platforms require the event loop to be run in the main thread. 24 | //! To ensure portability, the same restriction is enforced on all platforms. 25 | //! 26 | //! The easiest way to initialize the global context and run the event loop in the main thread 27 | //! is to use the [`main`] attribute macro on your main function. 28 | //! If you want to run some code in the main thread before the global context takes over, 29 | //! you can use the [`run_context()`] function or one of it's variations instead. 30 | //! Note that you must still call those functions from the main thread, 31 | //! and they do not return control back to the caller. 32 | //! 33 | //! # Event handling. 34 | //! You can register an event handler to run in the global context thread using [`WindowProxy::add_event_handler()`] or some of the similar functions. 35 | //! You can also register an event handler directly with the context to handle global events (including all window events). 36 | //! Since these event handlers run in the event loop, they should not block for any significant time. 37 | //! 38 | //! You can also receive events using [`WindowProxy::event_channel()`] or [`ContextProxy::event_channel()`]. 39 | //! These functions create a new channel for receiving window events or global events, respectively. 40 | //! As long as you're receiving the events in your own thread, you can block as long as you like. 41 | //! 42 | //! # Saving displayed images. 43 | //! If the `save` feature is enabled, windows allow the displayed image to be saved using `Ctrl+S` or `Ctrl+Shift+S`. 44 | //! The first shortcut will open a file dialog to save the currently displayed image. 45 | //! The second shortcut will directly save the image in the current working directory using the name of the image. 46 | //! 47 | //! The image is saved without any overlays. 48 | //! To save an image including overlays, add `Alt` to the shortcut: `Ctrl+Alt+S` and `Ctrl+Alt+Shift+S`. 49 | //! 50 | //! Note that images are saved in a background thread. 51 | //! To ensure that no data loss occurs, call [`exit()`] to terminate the process rather than [`std::process::exit()`]. 52 | //! That will ensure that the background threads are joined before the process is terminated. 53 | //! 54 | //! # Example 1: Showing an image. 55 | //! ```no_run 56 | //! # use image; 57 | //! use show_image::{ImageView, ImageInfo, create_window}; 58 | //! 59 | //! #[show_image::main] 60 | //! fn main() -> Result<(), Box> { 61 | //! 62 | //! # let pixel_data = &[0u8][..]; 63 | //! let image = ImageView::new(ImageInfo::rgb8(1920, 1080), pixel_data); 64 | //! 65 | //! // Create a window with default options and display the image. 66 | //! let window = create_window("image", Default::default())?; 67 | //! window.set_image("image-001", image)?; 68 | //! 69 | //! Ok(()) 70 | //! } 71 | //! ``` 72 | //! 73 | //! # Example 2: Handling keyboard events using an event channel. 74 | //! ```no_run 75 | //! # use show_image::{ImageInfo, ImageView}; 76 | //! use show_image::{event, create_window}; 77 | //! 78 | //! // Create a window and display the image. 79 | //! # let image = ImageView::new(ImageInfo::rgb8(1920, 1080), &[0u8][..]); 80 | //! let window = create_window("image", Default::default())?; 81 | //! window.set_image("image-001", &image)?; 82 | //! 83 | //! // Print keyboard events until Escape is pressed, then exit. 84 | //! // If the user closes the window, the channel is closed and the loop also exits. 85 | //! for event in window.event_channel()? { 86 | //! if let event::WindowEvent::KeyboardInput(event) = event { 87 | //! println!("{:#?}", event); 88 | //! if event.input.key_code == Some(event::VirtualKeyCode::Escape) && event.input.state.is_pressed() { 89 | //! break; 90 | //! } 91 | //! } 92 | //! } 93 | //! 94 | //! # Result::<(), Box>::Ok(()) 95 | //! ``` 96 | //! 97 | //! # Back-end and GPU selection 98 | //! 99 | //! This crate uses [`wgpu`] for rendering. 100 | //! You can force the selection of a specfic WGPU backend by setting the `WGPU_BACKEND` environment variable to one of the supported values: 101 | //! 102 | //! * `primary`: Use the primary backend for the platform (the default). 103 | //! * `vulkan`: Use the vulkan back-end. 104 | //! * `metal`: Use the metal back-end. 105 | //! * `dx12`: Use the DirectX 12 back-end. 106 | //! * `dx11`: Use the DirectX 11 back-end. 107 | //! * `gl`: Use the OpenGL back-end. 108 | //! * `webgpu`: Use the browser WebGPU back-end. 109 | //! 110 | //! You can also influence the GPU selection by setting the `WGPU_POWER_PREF` environment variable: 111 | //! 112 | //! * `low`: Prefer a low power GPU (the default). 113 | //! * `high`: Prefer a high performance GPU. 114 | 115 | #![cfg_attr(feature = "nightly", feature(doc_cfg))] 116 | #![cfg_attr(feature = "nightly", feature(termination_trait_lib))] 117 | #![warn(missing_docs)] 118 | 119 | mod backend; 120 | mod background_thread; 121 | pub mod error; 122 | pub mod event; 123 | mod features; 124 | mod image_info; 125 | mod image_types; 126 | mod oneshot; 127 | mod rectangle; 128 | 129 | pub use self::backend::*; 130 | #[allow(unused_imports)] 131 | pub use self::features::*; 132 | pub use self::image_info::*; 133 | pub use self::image_types::*; 134 | pub use self::rectangle::Rectangle; 135 | 136 | pub use winit; 137 | pub use winit::window::WindowId; 138 | 139 | pub use glam; 140 | 141 | /// An RGBA color. 142 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] 143 | pub struct Color { 144 | /// The red component in the range 0 to 1. 145 | pub red: f64, 146 | 147 | /// The green component in the range 0 to 1. 148 | pub green: f64, 149 | 150 | /// The blue component in the range 0 to 1. 151 | pub blue: f64, 152 | 153 | /// The alpha component in the range 0 to 1. 154 | pub alpha: f64, 155 | } 156 | 157 | impl Color { 158 | /// Create a new fully opaque color from the RGB components. 159 | pub const fn rgb(red: f64, green: f64, blue: f64) -> Self { 160 | Self::rgba(red, green, blue, 1.0) 161 | } 162 | 163 | /// Create a new color from the RGBA components. 164 | pub const fn rgba(red: f64, green: f64, blue: f64, alpha: f64) -> Self { 165 | Self { red, green, blue, alpha } 166 | } 167 | 168 | /// Get a color representing fully opaque black. 169 | pub const fn black() -> Self { 170 | Self::rgb(0.0, 0.0, 0.0) 171 | } 172 | 173 | /// Get a color representing fully opaque white. 174 | pub const fn white() -> Self { 175 | Self::rgb(1.0, 1.0, 1.0) 176 | } 177 | } 178 | 179 | pub mod termination; 180 | 181 | #[cfg(feature = "macros")] 182 | pub use show_image_macros::main; 183 | 184 | /// Save an image to the given path. 185 | #[cfg(feature = "save")] 186 | #[cfg_attr(feature = "nightly", doc(cfg(feature = "save")))] 187 | fn save_rgba8_image( 188 | path: impl AsRef, 189 | data: &[u8], 190 | size: glam::UVec2, 191 | row_stride: u32, 192 | ) -> Result<(), error::SaveImageError> { 193 | let path = path.as_ref(); 194 | 195 | let file = std::fs::File::create(path)?; 196 | 197 | let mut encoder = png::Encoder::new(file, size.x, size.y); 198 | encoder.set_color(png::ColorType::Rgba); 199 | encoder.set_depth(png::BitDepth::Eight); 200 | 201 | let mut writer = encoder.write_header()?; 202 | 203 | if row_stride == size.x * 4 { 204 | Ok(writer.write_image_data(data)?) 205 | } else { 206 | use std::io::Write; 207 | 208 | let mut writer = writer.into_stream_writer()?; 209 | for row in data.chunks(row_stride as usize) { 210 | let row = &row[..size.x as usize * 4]; 211 | writer.write_all(row)?; 212 | } 213 | writer.finish()?; 214 | Ok(()) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/oneshot.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::AtomicU8; 2 | use std::sync::atomic::Ordering; 3 | use std::sync::Arc; 4 | use std::sync::Condvar; 5 | use std::sync::Mutex; 6 | use std::sync::MutexGuard; 7 | 8 | const NOT_READY: u8 = 0; 9 | const FINISHED: u8 = 1; 10 | const DISCONNECTED: u8 = 2; 11 | 12 | pub fn channel() -> (Sender, Receiver) { 13 | let inner = Arc::new(Inner::new()); 14 | (Sender::new(inner.clone()), Receiver::new(inner)) 15 | } 16 | 17 | pub struct Sender { 18 | inner: Arc>, 19 | } 20 | 21 | pub struct Receiver { 22 | inner: Arc>, 23 | } 24 | 25 | struct Inner { 26 | state: AtomicU8, 27 | mutex: Mutex>, 28 | condvar: Condvar, 29 | } 30 | 31 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 32 | pub enum ReceiveError { 33 | Disconnected, 34 | AlreadyRetrieved, 35 | } 36 | 37 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 38 | pub enum TryReceiveError { 39 | Disconnected, 40 | AlreadyRetrieved, 41 | NotReady, 42 | } 43 | 44 | impl Inner { 45 | fn new() -> Self { 46 | Self { 47 | state: AtomicU8::new(0), 48 | mutex: Mutex::new(None), 49 | condvar: Condvar::new(), 50 | } 51 | } 52 | } 53 | 54 | impl Sender { 55 | fn new(inner: Arc>) -> Self { 56 | Self { inner } 57 | } 58 | 59 | pub fn send(self, value: T) { 60 | let mut lock = self.inner.mutex.lock().unwrap(); 61 | lock.replace(value); 62 | self.inner.state.store(FINISHED, Ordering::Release); 63 | self.inner.condvar.notify_all(); 64 | } 65 | } 66 | 67 | impl Drop for Sender { 68 | fn drop(&mut self) { 69 | let _ = self.inner.state.compare_exchange(NOT_READY, DISCONNECTED, Ordering::Release, Ordering::Relaxed); 70 | self.inner.condvar.notify_all(); 71 | } 72 | } 73 | 74 | impl Receiver { 75 | fn new(inner: Arc>) -> Self { 76 | Self { inner } 77 | } 78 | 79 | #[allow(unused)] 80 | pub fn recv(self) -> Result { 81 | let mut lock = self.inner.mutex.lock().unwrap(); 82 | loop { 83 | match self.internal_try_recv(&mut lock) { 84 | Ok(x) => return Ok(x), 85 | Err(TryReceiveError::Disconnected) => return Err(ReceiveError::Disconnected), 86 | Err(TryReceiveError::AlreadyRetrieved) => return Err(ReceiveError::AlreadyRetrieved), 87 | Err(TryReceiveError::NotReady) => lock = self.inner.condvar.wait(lock).unwrap(), 88 | } 89 | } 90 | } 91 | 92 | #[allow(unused)] 93 | pub fn try_recv(&mut self) -> Result { 94 | self.internal_try_recv(&mut self.inner.mutex.lock().unwrap()) 95 | } 96 | 97 | #[allow(unused)] 98 | pub fn recv_timeout(&mut self, timeout: std::time::Duration) -> Result { 99 | self.recv_deadline(std::time::Instant::now() + timeout) 100 | } 101 | 102 | #[allow(unused)] 103 | pub fn recv_deadline(&mut self, deadline: std::time::Instant) -> Result { 104 | let mut lock = self.inner.mutex.lock().unwrap(); 105 | loop { 106 | match self.internal_try_recv(&mut lock) { 107 | Ok(x) => return Ok(x), 108 | Err(TryReceiveError::Disconnected) => return Err(TryReceiveError::Disconnected), 109 | Err(TryReceiveError::AlreadyRetrieved) => return Err(TryReceiveError::AlreadyRetrieved), 110 | Err(TryReceiveError::NotReady) => { 111 | let now = std::time::Instant::now(); 112 | if now >= deadline { 113 | drop(lock); 114 | return Err(TryReceiveError::NotReady); 115 | } 116 | let (new_lock, timeout_result) = self.inner.condvar.wait_timeout(lock, deadline - now).unwrap(); 117 | if timeout_result.timed_out() { 118 | return Err(TryReceiveError::NotReady); 119 | } else { 120 | lock = new_lock; 121 | } 122 | }, 123 | } 124 | } 125 | } 126 | 127 | fn internal_try_recv(&self, lock: &mut MutexGuard>) -> Result { 128 | match self.inner.state.load(Ordering::Acquire) { 129 | FINISHED => lock.take().ok_or(TryReceiveError::AlreadyRetrieved), 130 | DISCONNECTED => Err(TryReceiveError::Disconnected), 131 | NOT_READY => Err(TryReceiveError::NotReady), 132 | x => unreachable!("invalid one-shot channel state: {}", x), 133 | } 134 | } 135 | } 136 | 137 | impl std::error::Error for ReceiveError {} 138 | impl std::error::Error for TryReceiveError {} 139 | 140 | impl std::fmt::Display for ReceiveError { 141 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 142 | match self { 143 | ReceiveError::Disconnected => write!(f, "the sender of the oneshot channel was dropped without setting a value"), 144 | ReceiveError::AlreadyRetrieved => write!(f, "the value of the oneshot channel has already been retrieved"), 145 | } 146 | } 147 | } 148 | 149 | impl std::fmt::Display for TryReceiveError { 150 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 151 | match self { 152 | TryReceiveError::Disconnected => ReceiveError::Disconnected.fmt(f), 153 | TryReceiveError::AlreadyRetrieved => ReceiveError::AlreadyRetrieved.fmt(f), 154 | TryReceiveError::NotReady => write!(f, "the value of the oneshot channel is not available yet"), 155 | } 156 | } 157 | } 158 | 159 | #[cfg(test)] 160 | mod test { 161 | use super::*; 162 | use assert2::assert; 163 | 164 | #[test] 165 | fn try_recv_value() { 166 | let (tx, mut rx) = channel(); 167 | tx.send(10); 168 | assert!(rx.try_recv() == Ok(10)); 169 | } 170 | 171 | #[test] 172 | fn try_recv_no_value() { 173 | let (_tx, mut rx) = channel::(); 174 | assert!(rx.try_recv() == Err(TryReceiveError::NotReady)); 175 | } 176 | 177 | #[test] 178 | fn try_recv_twice() { 179 | let (tx, mut rx) = channel::(); 180 | tx.send(10); 181 | assert!(rx.try_recv() == Ok(10)); 182 | assert!(rx.try_recv() == Err(TryReceiveError::AlreadyRetrieved)); 183 | } 184 | 185 | #[test] 186 | fn try_recv_disconnected() { 187 | let (tx, mut rx) = channel::(); 188 | drop(tx); 189 | assert!(rx.try_recv() == Err(TryReceiveError::Disconnected)); 190 | } 191 | 192 | #[test] 193 | fn recv() { 194 | let (tx, rx) = channel(); 195 | tx.send(10); 196 | assert!(rx.recv().ok() == Some(10)); 197 | } 198 | 199 | #[test] 200 | fn recv_timeout() { 201 | let (_tx, mut rx) = channel::(); 202 | assert!(rx.recv_timeout(std::time::Duration::from_millis(1)) == Err(TryReceiveError::NotReady)); 203 | } 204 | 205 | #[test] 206 | fn recv_multithreaded() { 207 | let (tx, mut rx) = channel::(); 208 | let thread = std::thread::spawn(|| { 209 | tx.send(12); 210 | }); 211 | assert!(rx.recv_timeout(std::time::Duration::from_millis(500)) == Ok(12)); 212 | let _ = thread.join(); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/rectangle.rs: -------------------------------------------------------------------------------- 1 | /// A rectangle. 2 | #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] 3 | pub struct Rectangle { 4 | x: i32, 5 | y: i32, 6 | width: u32, 7 | height: u32 8 | } 9 | 10 | impl Rectangle { 11 | /// Create a rectangle from X, Y coordinates and the width and height. 12 | pub fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Self { 13 | Self { x, y, width, height } 14 | } 15 | 16 | /// Get the X location of the rectangle. 17 | pub fn x(&self) -> i32 { 18 | self.x 19 | } 20 | 21 | /// Get the Y location of the rectangle. 22 | pub fn y(&self) -> i32 { 23 | self.y 24 | } 25 | 26 | /// Get the width of the rectangle. 27 | pub fn width(&self) -> u32 { 28 | self.width 29 | } 30 | 31 | /// Get the height of the rectangle. 32 | pub fn height(&self) -> u32 { 33 | self.height 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/termination.rs: -------------------------------------------------------------------------------- 1 | //! Local version of [`std::process::Termination`] until it is stabilized. 2 | //! 3 | //! On `nightly`, this module just re-exports the real [`std::process::Termination`] trait. 4 | 5 | #[cfg(feature = "nightly")] 6 | mod contents { 7 | pub use std::process::Termination; 8 | } 9 | 10 | #[cfg(not(feature = "nightly"))] 11 | mod contents { 12 | /// Dressed down version of [`std::process::Termination`]. 13 | /// 14 | /// This is used to allow user tasks to return `Result<(), E>` or just `()` on stable and beta. 15 | pub trait Termination { 16 | /// Print any messages to standard error and give the exit code for the process. 17 | fn report(self) -> i32; 18 | } 19 | 20 | impl Termination for () { 21 | fn report(self) -> i32 { 22 | 0 23 | } 24 | } 25 | 26 | impl Termination for Result<(), E> { 27 | fn report(self) -> i32 { 28 | if let Err(e) = self { 29 | eprintln!("Error: {:?}", e); 30 | 1 31 | } else { 32 | 0 33 | } 34 | } 35 | } 36 | } 37 | 38 | pub use contents::*; 39 | --------------------------------------------------------------------------------