1#![forbid(clippy::all)]
2#![allow(
3 clippy::collapsible_if,
4 clippy::many_single_char_names,
5 clippy::expect_fun_call,
6 clippy::useless_format,
7 clippy::new_without_default,
8 clippy::cognitive_complexity,
9 clippy::type_complexity,
10 clippy::or_fun_call,
11 clippy::nonminimal_bool,
12 clippy::single_match,
13 clippy::large_enum_variant
14)]
15
16pub mod execution;
17pub mod session;
18
19mod alloc;
20mod brush;
21mod cmd;
22mod color;
23mod cursor2d;
24mod data;
25mod event;
26mod font;
27mod framebuffer2d;
28mod image;
29mod palette;
30mod parser;
31mod platform;
32mod renderer;
33mod resources;
34mod screen2d;
35mod timer;
36mod view;
37
38#[macro_use]
39mod util;
40
41use cmd::Value;
42use event::Event;
43use execution::{DigestMode, Execution, ExecutionMode, GifMode};
44use platform::{LogicalSize, WindowEvent, WindowHint};
45use renderer::Renderer;
46use resources::ResourceManager;
47use session::*;
48use timer::FrameTimer;
49use view::FileStatus;
50
51use rgx;
52use rgx::core;
53use rgx::kit;
54
55#[macro_use]
56extern crate log;
57
58use directories as dirs;
59
60use std::alloc::System;
61use std::cell::RefCell;
62use std::path::{Path, PathBuf};
63use std::rc::Rc;
64use std::time;
65
66pub const VERSION: &str = "0.3.0";
68
69#[global_allocator]
70pub static ALLOCATOR: alloc::Allocator = alloc::Allocator::new(System);
71
72#[derive(Debug)]
73pub struct Options {
74 pub width: u32,
75 pub height: u32,
76 pub resizable: bool,
77 pub headless: bool,
78 pub source: Option<PathBuf>,
79 pub exec: ExecutionMode,
80 pub debug: bool,
81}
82
83impl Default for Options {
84 fn default() -> Self {
85 Self {
86 width: 1280,
87 height: 720,
88 headless: false,
89 resizable: true,
90 source: None,
91 exec: ExecutionMode::Normal,
92 debug: false,
93 }
94 }
95}
96
97pub fn init<P: AsRef<Path>>(paths: &[P], options: Options) -> std::io::Result<()> {
98 use std::io;
99
100 debug!("options: {:?}", options);
101
102 let hints = &[
103 WindowHint::Resizable(options.resizable),
104 WindowHint::Visible(!options.headless),
105 ];
106 let (win, events) = platform::init("rx", options.width, options.height, hints)?;
107
108 let hidpi_factor = win.hidpi_factor();
109 let win_size = win.size();
110 let (win_w, win_h) = (win_size.width as u32, win_size.height as u32);
111
112 info!("framebuffer size: {}x{}", win_size.width, win_size.height);
113 info!("hidpi factor: {}", hidpi_factor);
114
115 let resources = ResourceManager::new();
116 let base_dirs = dirs::ProjectDirs::from("io", "cloudhead", "rx")
117 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "home directory not found"))?;
118 let mut session = Session::new(win_w, win_h, hidpi_factor, resources.clone(), base_dirs)
119 .init(options.source.clone())?;
120
121 if options.debug {
122 session
123 .settings
124 .set("debug", Value::Bool(true))
125 .expect("'debug' is a bool'");
126 }
127
128 if let ExecutionMode::Record(_, _, GifMode::Record) = options.exec {
129 session
130 .settings
131 .set("input/delay", Value::F32(0.0))
132 .expect("'input/delay' is a float");
133 session
134 .settings
135 .set("vsync", Value::Bool(true))
136 .expect("'vsync' is a bool");
137 }
138
139 let exec = match options.exec {
140 ExecutionMode::Normal => Execution::normal(),
141 ExecutionMode::Replay(path, digest) => Execution::replaying(path, digest),
142 ExecutionMode::Record(path, digest, gif) => {
143 Execution::recording(path, digest, win_w as u16, win_h as u16, gif)
144 }
145 }?;
146
147 match &exec {
150 Execution::Replaying { digest, .. } | Execution::Recording { digest, .. }
151 if digest.mode != DigestMode::Ignore =>
152 {
153 session
154 .settings
155 .set("input/delay", Value::F32(0.0))
156 .expect("'input/delay' is a float");
157 session
158 .settings
159 .set("vsync", Value::Bool(false))
160 .expect("'vsync' is a bool");
161 session
162 .settings
163 .set("animation", Value::Bool(false))
164 .expect("'animation' is a bool");
165 }
166 _ => {}
167 }
168
169 let execution = Rc::new(RefCell::new(exec));
170 let mut present_mode = session.settings.present_mode();
171 let mut r = core::Renderer::new(win.handle())?;
172 let mut renderer = Renderer::new(&mut r, win_size, resources);
173
174 if let Err(e) = session.edit(paths) {
175 session.message(format!("Error loading path(s): {}", e), MessageType::Error);
176 }
177 if session.views.is_empty() {
178 session.blank(
179 FileStatus::NoFile,
180 Session::DEFAULT_VIEW_W,
181 Session::DEFAULT_VIEW_H,
182 );
183 }
184
185 renderer.init(session.effects(), &session.views, &mut r);
186
187 let physical = win_size.to_physical(hidpi_factor);
188 let mut logical = win_size;
189 let mut swap_chain = r.swap_chain(physical.width as u32, physical.height as u32, present_mode);
190
191 let mut render_timer = FrameTimer::new();
192 let mut update_timer = FrameTimer::new();
193 let mut session_events = Vec::with_capacity(16);
194 let mut last = time::Instant::now();
195
196 let exit = platform::run(win, events, move |w, event| {
197 if w.size().is_zero() {
199 return platform::ControlFlow::Wait;
200 }
201 if event.is_input() {
202 debug!("event: {:?}", event);
203 }
204
205 match event {
206 WindowEvent::Resized(size) => {
207 if size.is_zero() {
210 session.transition(State::Paused);
211 return platform::ControlFlow::Wait;
212 } else {
213 session.transition(State::Running);
214 }
215 }
216 WindowEvent::CursorEntered { .. } => {
217 w.set_cursor_visible(false);
220 }
221 WindowEvent::CursorLeft { .. } => {
222 w.set_cursor_visible(true);
223 }
224 WindowEvent::Ready => {
225 let scale = session.settings["scale"].float64();
226 let renderer_size = LogicalSize::new(
227 renderer.window.width * scale,
228 renderer.window.height * scale,
229 );
230 logical = w.size();
231
232 if logical != renderer_size {
233 self::resize(
234 &mut session,
235 &mut r,
236 &mut swap_chain,
237 logical,
238 hidpi_factor,
239 present_mode,
240 );
241 } else {
242 let input_delay: f64 = session.settings["input/delay"].float64();
243 std::thread::sleep(time::Duration::from_micros((input_delay * 1000.) as u64));
244 }
245
246 let delta = last.elapsed();
247 last = time::Instant::now();
248
249 if session.state == State::Paused {
252 return platform::ControlFlow::Wait;
253 }
254
255 let effects = update_timer
256 .run(|avg| session.update(&mut session_events, execution.clone(), delta, avg));
257 render_timer.run(|avg| {
258 renderer.frame(
259 &session,
260 execution.clone(),
261 effects,
262 &avg,
263 &mut r,
264 &mut swap_chain,
265 );
266 });
267
268 if session.settings_changed.contains("scale") {
269 self::resize(
270 &mut session,
271 &mut r,
272 &mut swap_chain,
273 logical,
274 hidpi_factor,
275 present_mode,
276 );
277 }
278
279 if session.settings_changed.contains("vsync") {
280 present_mode = session.settings.present_mode();
281
282 swap_chain = r.swap_chain(
283 swap_chain.width as u32,
284 swap_chain.height as u32,
285 present_mode,
286 );
287 }
288 }
289 WindowEvent::Minimized => {
290 session.transition(State::Paused);
291 return platform::ControlFlow::Wait;
292 }
293 WindowEvent::Restored => {
294 session.transition(State::Running);
295 }
296 WindowEvent::Focused(true) => {
297 session.transition(State::Running);
298 }
299 WindowEvent::Focused(false) => {
300 session.transition(State::Paused);
301 }
302 WindowEvent::RedrawRequested => {
303 }
306 WindowEvent::HiDpiFactorChanged(factor) => {
307 session.hidpi_factor = factor;
308 }
309 WindowEvent::CloseRequested => {
310 session.quit(ExitReason::Normal);
311 }
312 WindowEvent::CursorMoved { position } => {
313 session_events.push(Event::CursorMoved(position));
314 }
315 WindowEvent::MouseInput { state, button, .. } => {
316 session_events.push(Event::MouseInput(button, state));
317 }
318 WindowEvent::MouseWheel { delta, .. } => {
319 session_events.push(Event::MouseWheel(delta));
320 }
321 WindowEvent::KeyboardInput(input) => {
322 session_events.push(Event::KeyboardInput(input));
323 }
324 WindowEvent::ReceivedCharacter(c) => {
325 session_events.push(Event::ReceivedCharacter(c));
326 }
327 _ => {}
328 };
329
330 if let State::Closing(reason) = &session.state {
331 platform::ControlFlow::Exit(reason.clone())
332 } else {
333 platform::ControlFlow::Continue
334 }
335 });
336
337 match exit {
338 ExitReason::Normal => Ok(()),
339 ExitReason::Error(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
340 }
341}
342
343fn resize(
344 session: &mut Session,
345 r: &mut core::Renderer,
346 swap_chain: &mut core::SwapChain,
347 size: platform::LogicalSize,
348 hidpi_factor: f64,
349 present_mode: core::PresentMode,
350) {
351 let scale: f64 = session.settings["scale"].float64();
352 let logical_size = platform::LogicalSize::new(size.width / scale, size.height / scale);
353 session.handle_resized(logical_size);
354
355 let physical = size.to_physical(hidpi_factor);
356 *swap_chain = r.swap_chain(physical.width as u32, physical.height as u32, present_mode);
357}