rx/
lib.rs

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
66/// Program version.
67pub 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    // When working with digests, certain settings need to be overwritten
148    // to ensure things work correctly.
149    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        // Don't process events while the window is minimized.
198        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                // It's possible that the above check for zero size is delayed
208                // by a frame, in which case we need to catch things here.
209                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                // TODO: [winit] This doesn't fire if the cursor is already
218                // in the window.
219                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 we're paused, we want to keep the timer running to not get a
250                // "jump" when we unpause, but skip session updates and rendering.
251                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                // We currently don't draw in here, as it negatively
304                // affects resize smoothness.  (╯°□°)╯︵ ┻━┻
305            }
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}