#![forbid(clippy::all)]
#![allow(
clippy::collapsible_if,
clippy::many_single_char_names,
clippy::expect_fun_call,
clippy::useless_format,
clippy::new_without_default,
clippy::cognitive_complexity,
clippy::type_complexity,
clippy::or_fun_call,
clippy::nonminimal_bool,
clippy::single_match,
clippy::large_enum_variant
)]
pub mod execution;
pub mod session;
mod alloc;
mod brush;
mod cmd;
mod color;
mod cursor2d;
mod data;
mod event;
mod font;
mod framebuffer2d;
mod image;
mod palette;
mod parser;
mod platform;
mod renderer;
mod resources;
mod screen2d;
mod timer;
mod view;
#[macro_use]
mod util;
use cmd::Value;
use event::Event;
use execution::{DigestMode, Execution, ExecutionMode, GifMode};
use platform::{LogicalSize, WindowEvent, WindowHint};
use renderer::Renderer;
use resources::ResourceManager;
use session::*;
use timer::FrameTimer;
use view::FileStatus;
use rgx;
use rgx::core;
use rgx::kit;
#[macro_use]
extern crate log;
use directories as dirs;
use std::alloc::System;
use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::time;
pub const VERSION: &str = "0.3.0";
#[global_allocator]
pub static ALLOCATOR: alloc::Allocator = alloc::Allocator::new(System);
#[derive(Debug)]
pub struct Options {
pub width: u32,
pub height: u32,
pub resizable: bool,
pub headless: bool,
pub source: Option<PathBuf>,
pub exec: ExecutionMode,
pub debug: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
width: 1280,
height: 720,
headless: false,
resizable: true,
source: None,
exec: ExecutionMode::Normal,
debug: false,
}
}
}
pub fn init<P: AsRef<Path>>(paths: &[P], options: Options) -> std::io::Result<()> {
use std::io;
debug!("options: {:?}", options);
let hints = &[
WindowHint::Resizable(options.resizable),
WindowHint::Visible(!options.headless),
];
let (win, events) = platform::init("rx", options.width, options.height, hints)?;
let hidpi_factor = win.hidpi_factor();
let win_size = win.size();
let (win_w, win_h) = (win_size.width as u32, win_size.height as u32);
info!("framebuffer size: {}x{}", win_size.width, win_size.height);
info!("hidpi factor: {}", hidpi_factor);
let resources = ResourceManager::new();
let base_dirs = dirs::ProjectDirs::from("io", "cloudhead", "rx")
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "home directory not found"))?;
let mut session = Session::new(win_w, win_h, hidpi_factor, resources.clone(), base_dirs)
.init(options.source.clone())?;
if options.debug {
session
.settings
.set("debug", Value::Bool(true))
.expect("'debug' is a bool'");
}
if let ExecutionMode::Record(_, _, GifMode::Record) = options.exec {
session
.settings
.set("input/delay", Value::F32(0.0))
.expect("'input/delay' is a float");
session
.settings
.set("vsync", Value::Bool(true))
.expect("'vsync' is a bool");
}
let exec = match options.exec {
ExecutionMode::Normal => Execution::normal(),
ExecutionMode::Replay(path, digest) => Execution::replaying(path, digest),
ExecutionMode::Record(path, digest, gif) => {
Execution::recording(path, digest, win_w as u16, win_h as u16, gif)
}
}?;
match &exec {
Execution::Replaying { digest, .. } | Execution::Recording { digest, .. }
if digest.mode != DigestMode::Ignore =>
{
session
.settings
.set("input/delay", Value::F32(0.0))
.expect("'input/delay' is a float");
session
.settings
.set("vsync", Value::Bool(false))
.expect("'vsync' is a bool");
session
.settings
.set("animation", Value::Bool(false))
.expect("'animation' is a bool");
}
_ => {}
}
let execution = Rc::new(RefCell::new(exec));
let mut present_mode = session.settings.present_mode();
let mut r = core::Renderer::new(win.handle())?;
let mut renderer = Renderer::new(&mut r, win_size, resources);
if let Err(e) = session.edit(paths) {
session.message(format!("Error loading path(s): {}", e), MessageType::Error);
}
if session.views.is_empty() {
session.blank(
FileStatus::NoFile,
Session::DEFAULT_VIEW_W,
Session::DEFAULT_VIEW_H,
);
}
renderer.init(session.effects(), &session.views, &mut r);
let physical = win_size.to_physical(hidpi_factor);
let mut logical = win_size;
let mut swap_chain = r.swap_chain(physical.width as u32, physical.height as u32, present_mode);
let mut render_timer = FrameTimer::new();
let mut update_timer = FrameTimer::new();
let mut session_events = Vec::with_capacity(16);
let mut last = time::Instant::now();
let exit = platform::run(win, events, move |w, event| {
if w.size().is_zero() {
return platform::ControlFlow::Wait;
}
if event.is_input() {
debug!("event: {:?}", event);
}
match event {
WindowEvent::Resized(size) => {
if size.is_zero() {
session.transition(State::Paused);
return platform::ControlFlow::Wait;
} else {
session.transition(State::Running);
}
}
WindowEvent::CursorEntered { .. } => {
w.set_cursor_visible(false);
}
WindowEvent::CursorLeft { .. } => {
w.set_cursor_visible(true);
}
WindowEvent::Ready => {
let scale = session.settings["scale"].float64();
let renderer_size = LogicalSize::new(
renderer.window.width * scale,
renderer.window.height * scale,
);
logical = w.size();
if logical != renderer_size {
self::resize(
&mut session,
&mut r,
&mut swap_chain,
logical,
hidpi_factor,
present_mode,
);
} else {
let input_delay: f64 = session.settings["input/delay"].float64();
std::thread::sleep(time::Duration::from_micros((input_delay * 1000.) as u64));
}
let delta = last.elapsed();
last = time::Instant::now();
if session.state == State::Paused {
return platform::ControlFlow::Wait;
}
let effects = update_timer
.run(|avg| session.update(&mut session_events, execution.clone(), delta, avg));
render_timer.run(|avg| {
renderer.frame(
&session,
execution.clone(),
effects,
&avg,
&mut r,
&mut swap_chain,
);
});
if session.settings_changed.contains("scale") {
self::resize(
&mut session,
&mut r,
&mut swap_chain,
logical,
hidpi_factor,
present_mode,
);
}
if session.settings_changed.contains("vsync") {
present_mode = session.settings.present_mode();
swap_chain = r.swap_chain(
swap_chain.width as u32,
swap_chain.height as u32,
present_mode,
);
}
}
WindowEvent::Minimized => {
session.transition(State::Paused);
return platform::ControlFlow::Wait;
}
WindowEvent::Restored => {
session.transition(State::Running);
}
WindowEvent::Focused(true) => {
session.transition(State::Running);
}
WindowEvent::Focused(false) => {
session.transition(State::Paused);
}
WindowEvent::RedrawRequested => {
}
WindowEvent::HiDpiFactorChanged(factor) => {
session.hidpi_factor = factor;
}
WindowEvent::CloseRequested => {
session.quit(ExitReason::Normal);
}
WindowEvent::CursorMoved { position } => {
session_events.push(Event::CursorMoved(position));
}
WindowEvent::MouseInput { state, button, .. } => {
session_events.push(Event::MouseInput(button, state));
}
WindowEvent::MouseWheel { delta, .. } => {
session_events.push(Event::MouseWheel(delta));
}
WindowEvent::KeyboardInput(input) => {
session_events.push(Event::KeyboardInput(input));
}
WindowEvent::ReceivedCharacter(c) => {
session_events.push(Event::ReceivedCharacter(c));
}
_ => {}
};
if let State::Closing(reason) = &session.state {
platform::ControlFlow::Exit(reason.clone())
} else {
platform::ControlFlow::Continue
}
});
match exit {
ExitReason::Normal => Ok(()),
ExitReason::Error(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
}
}
fn resize(
session: &mut Session,
r: &mut core::Renderer,
swap_chain: &mut core::SwapChain,
size: platform::LogicalSize,
hidpi_factor: f64,
present_mode: core::PresentMode,
) {
let scale: f64 = session.settings["scale"].float64();
let logical_size = platform::LogicalSize::new(size.width / scale, size.height / scale);
session.handle_resized(logical_size);
let physical = size.to_physical(hidpi_factor);
*swap_chain = r.swap_chain(physical.width as u32, physical.height as u32, present_mode);
}