1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use std::{
    any::Any,
    error::Error,
    time::{Duration, SystemTime},
};

use ratatui::prelude::Backend;

use crate::{
    chunks::Chunks,
    events::Events,
    set::{Set, Sets},
    setup::{reset_terminal, restore_terminal, setup_terminal, WidgetFrame, WidgetTerminal},
    states::{States, Time},
    widget::{IntoWidgetSet, MultiFromStates, Widget},
    widgets::message::MessageState,
};

/// The powerhouse of widgetui, runs all defined widgets for you
pub struct App {
    terminal: WidgetTerminal,
    widgets: Vec<Box<dyn Widget>>,
    pub(crate) states: States,
    clock: Duration,
}

impl App {
    /// Create a new app with the given clock time (in ms)
    pub fn new(clock: u64) -> Result<Self, Box<dyn Error>> {
        let terminal = setup_terminal()?;
        Ok(Self {
            terminal,
            widgets: vec![],
            states: States::default(),
            clock: Duration::from_millis(clock),
        })
    }

    /// Running this will ensure that any panic that happens, this will catch
    /// And prevent your terminal from messing up.
    pub fn handle_panics(self) -> Self {
        let original_hook = std::panic::take_hook();

        std::panic::set_hook(Box::new(move |panic| {
            reset_terminal().unwrap();
            original_hook(panic);
        }));

        self
    }

    /// Adds the following Widgets to the system.
    /// This will take in a tuple of widgets, or a single widget.
    pub fn widgets<I>(mut self, widget: impl IntoWidgetSet<I>) -> Self {
        for widget in widget.into_widget_set() {
            self.widgets.push(widget);
        }
        self
    }

    /// Add the following states to the system
    /// This will take in a state or a tuple of states.
    pub fn states<S: MultiFromStates>(self, state: S) -> Self {
        state.insert_states(self)
    }

    /// Add a set to the system
    pub fn sets(self, set: impl Sets) -> Self {
        set.register_sets(self)
    }

    /// Run the app, returning an error if any of the functions error out.
    pub fn run(mut self) -> Result<(), Box<dyn Error>> {
        let result = self.inner_run();

        restore_terminal(self.terminal)?;

        result
    }

    fn inner_run(&mut self) -> Result<(), Box<dyn Error>> {
        self.terminal.hide_cursor()?;

        loop {
            self.terminal.autoresize()?;
            let mut frame = self.terminal.get_frame();

            {
                let mut chunks = self.states.get::<Chunks>()?;
                let mut chunks = chunks.get();

                chunks.clear();

                let mut events = self.states.get::<Events>()?;
                let mut events = events.get();

                let mut time = self.states.get::<Time>()?;
                let mut time = time.get();

                events.event = None;

                let start_time = SystemTime::now();

                if crossterm::event::poll(self.clock)? {
                    events.event = Some(crossterm::event::read()?);
                }

                let total_time = SystemTime::now().duration_since(start_time)?;

                time.set_duration(total_time);
            }

            self.states.get::<Chunks>()?.get();

            for widget in &mut self.widgets {
                widget.call(&mut frame, &mut self.states)?;
            }

            // Render Frame
            self.terminal.flush()?;

            self.terminal.swap_buffers();

            self.terminal.backend_mut().flush()?;

            // Handle App Events
            if self.states.get::<Events>()?.get().exit {
                return Ok(());
            }
        }
    }
}