use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::core::Element;
use super::app::App;
use super::filter::{EventFilter, FilterChain, FilterResult};
use super::frame_rate::FrameRateConfig;
#[derive(Clone, Default)]
pub struct CancelToken {
cancelled: Arc<AtomicBool>,
}
impl CancelToken {
pub fn new() -> Self {
Self {
cancelled: Arc::new(AtomicBool::new(false)),
}
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::SeqCst);
}
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::SeqCst)
}
pub(crate) fn flag(&self) -> Arc<AtomicBool> {
self.cancelled.clone()
}
}
#[derive(Debug, Clone)]
pub struct AppOptions {
pub fps: u32,
pub exit_on_ctrl_c: bool,
pub alternate_screen: bool,
pub adaptive_fps: bool,
pub min_fps: u32,
pub max_fps: u32,
pub collect_frame_stats: bool,
}
impl Default for AppOptions {
fn default() -> Self {
Self {
fps: 60, exit_on_ctrl_c: true,
alternate_screen: false, adaptive_fps: false,
min_fps: 10,
max_fps: 120,
collect_frame_stats: false,
}
}
}
impl AppOptions {
pub fn to_frame_rate_config(&self) -> FrameRateConfig {
let mut config = FrameRateConfig::new(self.fps);
if self.adaptive_fps {
config = config.adaptive(self.min_fps, self.max_fps);
}
if self.collect_frame_stats {
config = config.with_stats();
}
config
}
}
pub struct AppBuilder<F>
where
F: Fn() -> Element,
{
component: F,
options: AppOptions,
filter_chain: FilterChain,
cancel_token: Option<CancelToken>,
}
impl<F> AppBuilder<F>
where
F: Fn() -> Element,
{
pub fn new(component: F) -> Self {
Self {
component,
options: AppOptions::default(),
filter_chain: FilterChain::new(),
cancel_token: None,
}
}
pub fn fullscreen(mut self) -> Self {
self.options.alternate_screen = true;
self
}
pub fn inline(mut self) -> Self {
self.options.alternate_screen = false;
self
}
pub fn fps(mut self, fps: u32) -> Self {
self.options.fps = fps;
self
}
pub fn exit_on_ctrl_c(mut self, exit: bool) -> Self {
self.options.exit_on_ctrl_c = exit;
self
}
pub fn adaptive_fps(mut self, min_fps: u32, max_fps: u32) -> Self {
self.options.adaptive_fps = true;
self.options.min_fps = min_fps.clamp(1, 120);
self.options.max_fps = max_fps.clamp(self.options.min_fps, 120);
self
}
pub fn collect_frame_stats(mut self) -> Self {
self.options.collect_frame_stats = true;
self
}
pub fn with_filter<G>(mut self, name: &str, filter: G) -> Self
where
G: Fn(crossterm::event::Event) -> FilterResult + Send + Sync + 'static,
{
self.filter_chain.add(EventFilter::new(name, filter));
self
}
pub fn with_filter_priority<G>(mut self, name: &str, priority: i32, filter: G) -> Self
where
G: Fn(crossterm::event::Event) -> FilterResult + Send + Sync + 'static,
{
self.filter_chain
.add(EventFilter::with_priority(name, priority, filter));
self
}
pub fn with_cancel_token(mut self, token: CancelToken) -> Self {
self.cancel_token = Some(token);
self
}
pub fn options(&self) -> &AppOptions {
&self.options
}
pub fn run(self) -> std::io::Result<()> {
App::with_full_config(
self.component,
self.options,
self.filter_chain,
self.cancel_token,
)
.run()
}
}
pub fn render<F>(component: F) -> AppBuilder<F>
where
F: Fn() -> Element,
{
AppBuilder::new(component)
}
pub fn render_inline<F>(component: F) -> std::io::Result<()>
where
F: Fn() -> Element,
{
render(component).inline().run()
}
pub fn render_fullscreen<F>(component: F) -> std::io::Result<()>
where
F: Fn() -> Element,
{
render(component).fullscreen().run()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::components::Text;
#[test]
fn test_app_options_default() {
let options = AppOptions::default();
assert_eq!(options.fps, 60);
assert!(options.exit_on_ctrl_c);
assert!(!options.alternate_screen);
}
#[test]
fn test_app_builder_defaults() {
fn dummy() -> Element {
Text::new("test").into_element()
}
let builder = AppBuilder::new(dummy);
assert!(!builder.options().alternate_screen);
assert_eq!(builder.options().fps, 60);
}
#[test]
fn test_app_builder_fullscreen() {
fn dummy() -> Element {
Text::new("test").into_element()
}
let builder = AppBuilder::new(dummy).fullscreen();
assert!(builder.options().alternate_screen);
}
#[test]
fn test_app_builder_inline() {
fn dummy() -> Element {
Text::new("test").into_element()
}
let builder = AppBuilder::new(dummy).fullscreen().inline();
assert!(!builder.options().alternate_screen);
}
#[test]
fn test_app_builder_fps() {
fn dummy() -> Element {
Text::new("test").into_element()
}
let builder = AppBuilder::new(dummy).fps(30);
assert_eq!(builder.options().fps, 30);
}
#[test]
fn test_cancel_token_creation() {
let token = CancelToken::new();
assert!(!token.is_cancelled());
}
#[test]
fn test_cancel_token_cancel() {
let token = CancelToken::new();
assert!(!token.is_cancelled());
token.cancel();
assert!(token.is_cancelled());
}
#[test]
fn test_cancel_token_clone() {
let token = CancelToken::new();
let token2 = token.clone();
assert!(!token.is_cancelled());
assert!(!token2.is_cancelled());
token.cancel();
assert!(token.is_cancelled());
assert!(token2.is_cancelled());
}
#[test]
fn test_cancel_token_default() {
let token = CancelToken::default();
assert!(!token.is_cancelled());
}
}