1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
//! MiniView is a small program which allows to show a single image in a graphical window.
//! It supports both windowed mode and fullscreen mode and can be useful for debugging or testing
//! programs dealing with images.
//!
//! MiniView can be used both as a binary executable (usually `miniview` or `miniview.exe`),
//! or as a library.
//!
//! If you want to use a `miniview` binary, provide the `--help` flag for information on its usage
//! and options. In addition, you could take a look at the [`readme`].
//!
//! For library usage you may want to start by looking at the [`MiniView.show`] method and
//! [`ConfigBuilder`] struct, to respectively create a `MiniView` window controlling instance and
//! conveniently create a configuration which is required for `MiniView.show`.
//!
//! Feel free to post questions, issues, suggestions and feedback at the [`issue tracker`].
//!
//! # Example usage:
//!
//! ```rust
//! use miniview::{ConfigBuilder, MiniView};
//! use std::time::Duration;
//!
//! let config = ConfigBuilder::from_path(concat!(env!("CARGO_MANIFEST_DIR"), "/resources/plant.jpg"))
//! .set_fullscreen(true)
//! .build();
//!
//! let controls = MiniView::show(config).expect("unable to create miniview");
//!
//! // do some important other work!
//! std::thread::sleep(Duration::from_millis(1000));
//!
//! let closed = controls.close();
//! assert!(closed.is_ok());
//! ```
//!
//! # Backends
//!
//! MiniView supports two backends: piston-window and pixels. You can switch between backends on compile time. This requires
//! setting Cargo [features](https://doc.rust-lang.org/cargo/reference/features.html). The piston-window backend can be
//! enabled using the `backend_piston_window` feature, and the pixels backend can be enabled using the `backend_pixels` feature.
//!
//! The default backend is **pixels**. This backend will be used if no-default-features is not specified.
//!
//! The next sections provide examples, on how to enable each backend. Only one backend should be enabled at a time.
//!
//! ## backend: piston-window
//!
//! ### Platform support
//!
//! Supported platforms:
//! * any platform supported by [piston-window](https://github.com/PistonDevelopers/piston_window) with Glutin, including:
//! * Linux
//! * MacOS
//! * Windows
//!
//! ### Configuration examples
//!
//! When building MiniView, the piston-window backend can be used by compiling with:
//! ```bash
//! cargo run --no-default-features --features backend_piston_window
//! ```
//!
//! When using MiniView as a library, you can use:
//! ```toml
//! [dependencies.miniview]
//! version = "*" # select the latest version here
//! default-features = false
//! features = ["backend_piston_window"]
//! ```
//!
//! or
//!
//! ```toml
//! [dependencies]
//! miniview = { version = "*", default-features = false, features = ["backend_piston_window"] }
//! ```
//!
//! ## backend: pixels
//!
//! ### Platform support
//!
//! Supported platforms:
//! * Linux
//! * Dragonfly
//! * FreeBSD
//! * NetBSD
//! * OpenBSD
//! * Windows
//!
//! Note: MacOS is not yet supported for this backend.
//!
//! ### Configuration examples
//!
//! When building MiniView, the pixels backend can be used by compiling with:
//! ```bash
//! cargo run --no-default-features --features backend_pixels
//! ```
//!
//! When using MiniView as a library, you can use:
//! ```toml
//! [dependencies.miniview]
//! version = "*" # select the latest version here
//! default-features = false
//! features = ["backend_pixels"]
//! ```
//!
//! or
//!
//! ```toml
//! [dependencies]
//! miniview = { version = "*", default-features = false, features = ["backend_pixels"] }
//! ```
//!
//! [`issue tracker`]: https://github.com/foresterre/miniview/issues
//! [`readme`]: https://github.com/foresterre/miniview/blob/main/README.md
//! [`MiniView.show`]: struct.MiniView.html#method.show
//! [`ConfigBuilder`]: config/struct.ConfigBuilder.html
#![allow(clippy::upper_case_acronyms)]
extern crate image as imagecrate; // There is also an image module in piston_window
use crate::config::Config;
use crate::errors::ImportError;
use crate::io::import_image_from_stdin_bytes_block;
use imagecrate::DynamicImage;
use std::fmt::Debug;
use std::path::PathBuf;
use std::sync::mpsc;
use std::thread;
pub use crate::config::ConfigBuilder;
pub use crate::errors::MiniViewError;
#[cfg(feature = "backend_piston_window")]
pub(crate) mod backend_piston_window;
#[cfg(feature = "backend_pixels")]
pub(crate) mod backend_pixels;
pub mod config;
pub mod errors;
pub mod io;
/// A convenience type alias which represents a regular [`Result`] where the error type is
/// represented by the [`MiniViewError`], which is the top-level error type for this crate.
///
/// [`Result`]: https://doc.rust-lang.org/stable/core/result/enum.Result.html
/// [`MiniViewError`]: errors/enum.MiniViewError.html
pub type MVResult<T> = Result<T, MiniViewError>;
trait ResizableWhen {
fn resizable_when<P: Fn() -> bool>(self, predicate: P) -> Self;
}
trait FullscreenWhen {
fn fullscreen_when<P: Fn() -> bool>(self, predicate: P) -> Self;
}
/// The source of an image which will be shown by the view
#[derive(Debug, Clone)]
pub enum Source {
/// A path which points at an image file, e.g. `/home/myuser/image.png` or
/// `C:/Users/MyUser/image.png`.
ByPath(PathBuf),
/// A raw (as in an image formatted using a supported encoding as byte stream) image piped or
/// otherwise provided to the stdin
StdinBytes,
}
impl Source {
/// Load the image to memory
fn open(&self) -> MVResult<DynamicImage> {
match &self {
Source::ByPath(path) => imagecrate::open(path.as_path())
.map_err(|_| MiniViewError::FailedToImport(ImportError::OnPathNotFound)),
Source::StdinBytes => import_image_from_stdin_bytes_block(),
}
}
}
#[derive(Debug, Clone, Copy)]
enum Action {
Close,
}
/// Provides the controls to show and consecutively close a `miniview` window
///
/// For more, see [`show`].
///
/// [`show`]: struct.MiniView.html#method.show
pub struct MiniView {
sender: mpsc::Sender<Action>,
handle: thread::JoinHandle<Result<(), MiniViewError>>,
}
impl MiniView {
/// Create the controls to a new `miniview` window
///
/// This will spawn a thread which will manage and create a graphical window. The
/// [`MiniView`] struct on the main thread can be used to control the window.
///
/// The window can be closed explicitly by calling [`close`] or we can wait until the user will
/// close the window manually by using [`wait_for_exit`] instead.
///
/// When a [`MiniView`] instance goes out of scope and is dropped, the thread managing the
/// graphical image view window will also die.
///
/// [`MiniView`]: struct.MiniView.html
/// [`close`]: struct.MiniView.html#method.close
/// [`wait_for_exit`]: struct.MiniView.html#method.wait_for_exit
pub fn show(config: Config) -> MVResult<Self> {
#[cfg(feature = "backend_piston_window")]
{
backend_piston_window::show(config)
}
#[cfg(feature = "backend_pixels")]
{
backend_pixels::show(config)
}
}
/// Sends a 'close window' event to the thread managing the graphical window and waits for the
/// thread to return
///
/// Compared to [`wait_for_exit`] this method will explicitly attempt to close the window,
/// and should close almost instantly. This method blocks the until the window has been closed,
/// and the thread has been returned, _or_ an error has been returned instead.
///
/// [`wait_for_exit`]: struct.MiniView.html#method.wait_for_exit
pub fn close(self) -> MVResult<()> {
close(self)
}
/// Waits until the thread managing the graphical window returns
///
/// Compared to [`close`] which attempts to instantaneously close the window regardless of user
/// input, this method will block and wait for the user to close the window.
///
/// [`close`]: struct.MiniView.html#method.close
pub fn wait_for_exit(self) -> MVResult<()> {
wait_for_exit(self)
}
}
pub(crate) fn close(mini_view: MiniView) -> MVResult<()> {
mini_view
.sender
.send(Action::Close)
.map_err(|_err| MiniViewError::SendStopError)?;
mini_view
.handle
.join()
.map_err(|_err| MiniViewError::ViewThreadFailedToJoin)
.and_then(|inner| inner)
}
pub(crate) fn wait_for_exit(mini_view: MiniView) -> MVResult<()> {
mini_view
.handle
.join()
.map_err(|_err| MiniViewError::ViewThreadFailedToJoin)
.and_then(|inner| inner)
}