/ src / lib.rs
lib.rs
  1  #![deny(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::comparison_chain,
 10      clippy::type_complexity,
 11      clippy::or_fun_call,
 12      clippy::nonminimal_bool,
 13      clippy::single_match,
 14      clippy::large_enum_variant
 15  )]
 16  
 17  pub mod data;
 18  pub mod execution;
 19  pub mod gfx;
 20  pub mod logger;
 21  pub mod session;
 22  
 23  mod alloc;
 24  mod autocomplete;
 25  mod brush;
 26  mod cmd;
 27  mod color;
 28  mod draw;
 29  mod event;
 30  mod flood;
 31  mod font;
 32  mod gl;
 33  mod history;
 34  mod image;
 35  mod io;
 36  mod palette;
 37  mod parser;
 38  mod pixels;
 39  mod platform;
 40  mod renderer;
 41  mod sprite;
 42  mod timer;
 43  mod view;
 44  
 45  #[macro_use]
 46  pub mod util;
 47  
 48  use cmd::Value;
 49  use event::Event;
 50  use execution::{DigestMode, Execution, ExecutionMode};
 51  use platform::{WindowEvent, WindowHint};
 52  use renderer::Renderer;
 53  use session::*;
 54  use timer::FrameTimer;
 55  use view::FileStatus;
 56  
 57  #[macro_use]
 58  extern crate log;
 59  
 60  use directories as dirs;
 61  
 62  use std::alloc::System;
 63  use std::path::{Path, PathBuf};
 64  use std::time::{Duration, Instant};
 65  
 66  /// Program version.
 67  pub const VERSION: &str = env!("CARGO_PKG_VERSION");
 68  
 69  #[global_allocator]
 70  pub static ALLOCATOR: alloc::Allocator = alloc::Allocator::new(System);
 71  
 72  #[derive(Debug)]
 73  pub struct Options<'a> {
 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 glyphs: &'a [u8],
 81      pub debug: bool,
 82  }
 83  
 84  impl<'a> Default for Options<'a> {
 85      fn default() -> Self {
 86          Self {
 87              width: 1280,
 88              height: 720,
 89              headless: false,
 90              resizable: true,
 91              source: None,
 92              exec: ExecutionMode::Normal,
 93              glyphs: data::GLYPHS,
 94              debug: false,
 95          }
 96      }
 97  }
 98  
 99  pub fn init<P: AsRef<Path>>(paths: &[P], options: Options<'_>) -> std::io::Result<()> {
100      use std::io;
101  
102      debug!("options: {:?}", options);
103  
104      let hints = &[
105          WindowHint::Resizable(options.resizable),
106          WindowHint::Visible(!options.headless),
107      ];
108      let (mut win, mut events) = platform::init(
109          "rx",
110          options.width,
111          options.height,
112          hints,
113          platform::GraphicsContext::Gl,
114      )?;
115  
116      let scale_factor = win.scale_factor();
117      let win_size = win.size();
118      let (win_w, win_h) = (win_size.width as u32, win_size.height as u32);
119  
120      info!("framebuffer size: {}x{}", win_size.width, win_size.height);
121      info!("scale factor: {}", scale_factor);
122  
123      let assets = data::Assets::new(options.glyphs);
124      let proj_dirs = dirs::ProjectDirs::from("io", "cloudhead", "rx")
125          .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "config directory not found"))?;
126      let base_dirs = dirs::BaseDirs::new()
127          .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "home directory not found"))?;
128      let cwd = std::env::current_dir()?;
129      let mut session = Session::new(win_w, win_h, cwd, proj_dirs, base_dirs)
130          .with_blank(
131              FileStatus::NoFile,
132              Session::DEFAULT_VIEW_W,
133              Session::DEFAULT_VIEW_H,
134          )
135          .init(options.source.clone())?;
136  
137      if options.debug {
138          session
139              .settings
140              .set("debug", Value::Bool(true))
141              .expect("'debug' is a bool'");
142      }
143  
144      let mut execution = match options.exec {
145          ExecutionMode::Normal => Execution::normal(),
146          ExecutionMode::Replay(path, digest) => Execution::replaying(path, digest),
147          ExecutionMode::Record(path, digest, gif) => {
148              Execution::recording(path, digest, win_w as u16, win_h as u16, gif)
149          }
150      }?;
151  
152      // When working with digests, certain settings need to be overwritten
153      // to ensure things work correctly.
154      match &execution {
155          Execution::Replaying { digest, .. } | Execution::Recording { digest, .. }
156              if digest.mode != DigestMode::Ignore =>
157          {
158              session
159                  .settings
160                  .set("animation", Value::Bool(false))
161                  .expect("'animation' is a bool");
162          }
163          _ => {}
164      }
165  
166      let wait_events = execution.is_normal() || execution.is_recording();
167  
168      let mut renderer: gl::Renderer = Renderer::new(&mut win, win_size, scale_factor, assets)?;
169  
170      if let Err(e) = session.edit(paths) {
171          session.message(format!("Error loading path(s): {}", e), MessageType::Error);
172      }
173      // Make sure our session ticks once before anything is rendered.
174      let effects = session.update(
175          &mut vec![],
176          &mut execution,
177          Duration::default(),
178          Duration::default(),
179      );
180      renderer.init(effects, &session);
181  
182      let mut render_timer = FrameTimer::new();
183      let mut update_timer = FrameTimer::new();
184      let mut session_events = Vec::with_capacity(16);
185      let mut last = Instant::now();
186      let mut resized = false;
187      let mut hovering = false;
188      let mut delta;
189  
190      while !win.is_closing() {
191          match session.animation_delay() {
192              Some(delay) if session.is_running() => {
193                  // How much time is left until the next animation frame?
194                  let remaining = delay - session.accumulator;
195                  // If more than 1ms remains, let's wait.
196                  if remaining.as_millis() > 1 {
197                      events.wait_timeout(remaining);
198                  } else {
199                      events.poll();
200                  }
201              }
202              _ if wait_events => events.wait(),
203              _ => events.poll(),
204          }
205  
206          for event in events.flush() {
207              if event.is_input() {
208                  debug!("event: {:?}", event);
209              }
210  
211              match event {
212                  WindowEvent::Resized(size) => {
213                      if size.is_zero() {
214                          // On certain operating systems, the window size will be set to
215                          // zero when the window is minimized. Since a zero-sized framebuffer
216                          // is not valid, we pause the session until the window is restored.
217                          session.transition(State::Paused);
218                      } else {
219                          resized = true;
220                          session.transition(State::Running);
221                      }
222                  }
223                  WindowEvent::CursorEntered { .. } => {
224                      if win.is_focused() {
225                          win.set_cursor_visible(false);
226                      }
227                      hovering = true;
228                  }
229                  WindowEvent::CursorLeft { .. } => {
230                      win.set_cursor_visible(true);
231  
232                      hovering = false;
233                  }
234                  WindowEvent::Minimized => {
235                      session.transition(State::Paused);
236                  }
237                  WindowEvent::Restored => {
238                      if win.is_focused() {
239                          session.transition(State::Running);
240                      }
241                  }
242                  WindowEvent::Focused(true) => {
243                      session.transition(State::Running);
244  
245                      if hovering {
246                          win.set_cursor_visible(false);
247                      }
248                  }
249                  WindowEvent::Focused(false) => {
250                      win.set_cursor_visible(true);
251                      session.transition(State::Paused);
252                  }
253                  WindowEvent::RedrawRequested => {
254                      render_timer.run(|avg| {
255                          renderer
256                              .frame(&mut session, &mut execution, vec![], &avg)
257                              .unwrap_or_else(|err| {
258                                  log::error!("{}", err);
259                              });
260                      });
261                      win.present();
262                  }
263                  WindowEvent::ScaleFactorChanged(factor) => {
264                      renderer.handle_scale_factor_changed(factor);
265                  }
266                  WindowEvent::CloseRequested => {
267                      session.quit(ExitReason::Normal);
268                  }
269                  WindowEvent::CursorMoved { position } => {
270                      session_events.push(Event::CursorMoved(position));
271                  }
272                  WindowEvent::MouseInput { state, button, .. } => {
273                      session_events.push(Event::MouseInput(button, state));
274                  }
275                  WindowEvent::MouseWheel { delta, .. } => {
276                      session_events.push(Event::MouseWheel(delta));
277                  }
278                  WindowEvent::KeyboardInput(input) => match input {
279                      // Intercept `<insert>` key for pasting.
280                      //
281                      // Reading from the clipboard causes the loop to wake up for some strange
282                      // reason I cannot comprehend. So we only read from clipboard when we
283                      // need to paste.
284                      platform::KeyboardInput {
285                          key: Some(platform::Key::Insert),
286                          state: platform::InputState::Pressed,
287                          modifiers: platform::ModifiersState { shift: true, .. },
288                      } => {
289                          session_events.push(Event::Paste(win.clipboard()));
290                      }
291                      _ => session_events.push(Event::KeyboardInput(input)),
292                  },
293                  WindowEvent::ReceivedCharacter(c, mods) => {
294                      session_events.push(Event::ReceivedCharacter(c, mods));
295                  }
296                  _ => {}
297              };
298          }
299  
300          if resized {
301              // Instead of responded to each resize event by creating a new framebuffer,
302              // we respond to the event *once*, here.
303              resized = false;
304              session.handle_resized(win.size());
305          }
306  
307          delta = last.elapsed();
308          last += delta;
309  
310          // If we're paused, we want to keep the timer running to not get a
311          // "jump" when we unpause, but skip session updates and rendering.
312          if session.state == State::Paused {
313              continue;
314          }
315  
316          let effects =
317              update_timer.run(|avg| session.update(&mut session_events, &mut execution, delta, avg));
318  
319          render_timer.run(|avg| {
320              renderer
321                  .frame(&mut session, &mut execution, effects, &avg)
322                  .unwrap_or_else(|err| {
323                      log::error!("{}", err);
324                  });
325          });
326  
327          session.cleanup();
328          win.present();
329  
330          match session.state {
331              State::Closing(ExitReason::Normal) => {
332                  return Ok(());
333              }
334              State::Closing(ExitReason::Error(e)) => {
335                  return Err(io::Error::new(io::ErrorKind::Other, e));
336              }
337              _ => {}
338          }
339      }
340  
341      Ok(())
342  }