use crate::element::Element;
use crate::input::{poll_key, Key};
use crate::renderer::Blaeck;
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use std::io::{self, Write};
use std::time::Duration;
#[derive(Clone)]
pub struct AppConfig {
pub poll_interval: Duration,
pub exit_on_ctrl_c: bool,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
poll_interval: Duration::from_millis(50),
exit_on_ctrl_c: true,
}
}
}
pub struct AppResult {
pub exit_reason: ExitReason,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExitReason {
UserExit,
Completed,
}
pub struct App<W: Write> {
blaeck: Blaeck<W>,
config: AppConfig,
should_exit: bool,
exit_reason: ExitReason,
}
impl App<io::Stdout> {
pub fn new() -> io::Result<Self> {
Self::with_config(AppConfig::default())
}
pub fn with_config(config: AppConfig) -> io::Result<Self> {
let stdout = io::stdout();
let blaeck = Blaeck::new(stdout)?;
Ok(Self {
blaeck,
config,
should_exit: false,
exit_reason: ExitReason::Completed,
})
}
}
impl<W: Write> App<W> {
pub fn with_writer(writer: W, config: AppConfig) -> io::Result<Self> {
let blaeck = Blaeck::new(writer)?;
Ok(Self {
blaeck,
config,
should_exit: false,
exit_reason: ExitReason::Completed,
})
}
pub fn exit(&mut self) {
self.should_exit = true;
self.exit_reason = ExitReason::UserExit;
}
pub fn should_exit(&self) -> bool {
self.should_exit
}
pub fn blaeck(&self) -> &Blaeck<W> {
&self.blaeck
}
pub fn blaeck_mut(&mut self) -> &mut Blaeck<W> {
&mut self.blaeck
}
pub fn run<R, I>(mut self, mut render: R, mut on_input: I) -> io::Result<AppResult>
where
R: FnMut(&mut Self) -> Element,
I: FnMut(&mut Self, Key),
{
enable_raw_mode()?;
let ui = render(&mut self);
self.blaeck.render(ui)?;
while !self.should_exit {
if let Some(key) = poll_key(self.config.poll_interval)? {
if self.config.exit_on_ctrl_c && key.is_ctrl_c() {
self.should_exit = true;
self.exit_reason = ExitReason::UserExit;
break;
}
on_input(&mut self, key);
let ui = render(&mut self);
self.blaeck.render(ui)?;
}
}
disable_raw_mode()?;
self.blaeck.unmount()?;
Ok(AppResult {
exit_reason: self.exit_reason,
})
}
pub fn run_simple<R>(self, render: R) -> io::Result<AppResult>
where
R: FnMut(&mut Self) -> Element,
{
self.run(render, |_, _| {})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_app_config_default() {
let config = AppConfig::default();
assert_eq!(config.poll_interval, Duration::from_millis(50));
assert!(config.exit_on_ctrl_c);
}
#[test]
fn test_app_config_custom() {
let config = AppConfig {
poll_interval: Duration::from_millis(100),
exit_on_ctrl_c: false,
};
assert_eq!(config.poll_interval, Duration::from_millis(100));
assert!(!config.exit_on_ctrl_c);
}
#[test]
fn test_exit_reason_eq() {
assert_eq!(ExitReason::UserExit, ExitReason::UserExit);
assert_ne!(ExitReason::UserExit, ExitReason::Completed);
}
#[test]
fn test_exit_reason_clone() {
let reason = ExitReason::Completed;
let cloned = reason.clone();
assert_eq!(reason, cloned);
}
#[test]
fn test_app_with_writer() {
let buf = Vec::new();
let config = AppConfig::default();
let app = App::with_writer(buf, config);
assert!(app.is_ok());
}
#[test]
fn test_app_should_exit_default() {
let buf = Vec::new();
let config = AppConfig::default();
let app = App::with_writer(buf, config).unwrap();
assert!(!app.should_exit());
}
#[test]
fn test_app_exit() {
let buf = Vec::new();
let config = AppConfig::default();
let mut app = App::with_writer(buf, config).unwrap();
assert!(!app.should_exit());
app.exit();
assert!(app.should_exit());
}
#[test]
fn test_app_blaeck_access() {
let buf = Vec::new();
let config = AppConfig::default();
let app = App::with_writer(buf, config).unwrap();
let _width = app.blaeck().width();
}
#[test]
fn test_app_blaeck_mut_access() {
let buf = Vec::new();
let config = AppConfig::default();
let mut app = App::with_writer(buf, config).unwrap();
let _blaeck = app.blaeck_mut();
}
#[test]
fn test_app_result_exit_reason() {
let result = AppResult {
exit_reason: ExitReason::UserExit,
};
assert_eq!(result.exit_reason, ExitReason::UserExit);
}
#[test]
fn test_app_config_clone() {
let config = AppConfig::default();
let cloned = config.clone();
assert_eq!(config.poll_interval, cloned.poll_interval);
assert_eq!(config.exit_on_ctrl_c, cloned.exit_on_ctrl_c);
}
}