summaryrefslogtreecommitdiffstats
path: root/src/app/event.rs
blob: a6afb2d84fe0fc0160966243c96fe54ecc8d3395 (plain)
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use std::time::{Duration, Instant};

const MAX_TIMEOUT: Duration = Duration::from_millis(400);

/// These are "signals" that are sent along with an [`WidgetEventResult`] to signify a potential additional action
/// that the caller must do, along with the "core" result of either drawing or redrawing.
#[derive(Debug)]
pub enum ReturnSignal {
    /// A signal returned when some process widget was told to try to kill a process (or group of processes).
    ///
    /// This return signal should trigger a redraw when handled.
    KillProcess,

    /// A signal returned when a widget needs the app state to re-trigger its update call. Usually needed for
    /// widgets where the displayed contents are built only on update.
    ///
    /// This return signal should trigger a redraw when handled.
    Update,
}

/// The results of handling an event by the [`AppState`].
#[derive(Debug)]
pub enum EventResult {
    /// Kill the program.
    Quit,
    /// Trigger a redraw.
    Redraw,
    /// Don't trigger a redraw.
    NoRedraw,
}

/// The results of a widget handling some event, like a mouse or key event,
/// signifying what the program should then do next.
#[derive(Debug)]
pub enum WidgetEventResult {
    /// Kill the program.
    Quit,
    /// Trigger a redraw.
    Redraw,
    /// Don't trigger a redraw.
    NoRedraw,
    /// Return a signal.
    Signal(ReturnSignal),
}

/// How a widget should handle a widget selection request.
pub enum SelectionAction {
    /// This event occurs if the widget internally handled the selection action.
    Handled,
    /// This event occurs if the widget did not handle the selection action; the caller must handle it.
    NotHandled,
}

/// The states a [`MultiKey`] can be in.
enum MultiKeyState {
    /// Currently not waiting on any next input.
    Idle,
    /// Waiting for the next input, with a given trigger [`Instant`].
    Waiting {
        /// When it was triggered.
        trigger_instant: Instant,

        /// What part of the pattern it is at.
        checked_index: usize,
    },
}

/// The possible outcomes of calling [`MultiKey::input`].
pub enum MultiKeyResult {
    /// Returned when a character was *accepted*, but has not completed the sequence required.
    Accepted,
    /// Returned when a character is accepted and completes the sequence.
    Completed,
    /// Returned if a character breaks the sequence or if it has already timed out.
    Rejected,
}

/// A struct useful for managing multi-key keybinds.
pub struct MultiKey {
    state: MultiKeyState,
    pattern: Vec<char>,
}

impl MultiKey {
    /// Creates a new [`MultiKey`] with a given pattern and timeout.
    pub fn register(pattern: Vec<char>) -> Self {
        Self {
            state: MultiKeyState::Idle,
            pattern,
        }
    }

    /// Resets to an idle state.
    fn reset(&mut self) {
        self.state = MultiKeyState::Idle;
    }

    /// Handles a char input and returns the current status of the [`MultiKey`] after, which is one of:
    /// - Accepting the char and moving to the next state
    /// - Completing the multi-key pattern
    /// - Rejecting it
    ///
    /// Note that if a [`MultiKey`] only "times out" upon calling this - if it has timed out, it will first reset
    /// before trying to check the char.
    pub fn input(&mut self, c: char) -> MultiKeyResult {
        match &mut self.state {
            MultiKeyState::Idle => {
                if let Some(first) = self.pattern.first() {
                    if *first == c {
                        self.state = MultiKeyState::Waiting {
                            trigger_instant: Instant::now(),
                            checked_index: 0,
                        };

                        return MultiKeyResult::Accepted;
                    }
                }

                MultiKeyResult::Rejected
            }
            MultiKeyState::Waiting {
                trigger_instant,
                checked_index,
            } => {
                if trigger_instant.elapsed() > MAX_TIMEOUT {
                    // Just reset and recursively call (putting it into Idle).
                    self.reset();
                    self.input(c)
                } else if let Some(next) = self.pattern.get(*checked_index + 1) {
                    if *next == c {
                        *checked_index += 1;

                        if *checked_index == self.pattern.len() - 1 {
                            self.reset();
                            MultiKeyResult::Completed
                        } else {
                            MultiKeyResult::Accepted
                        }
                    } else {
                        self.reset();
                        MultiKeyResult::Rejected
                    }
                } else {
                    self.reset();
                    MultiKeyResult::Rejected
                }
            }
        }
    }
}