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
151
152
153
154
155
156
157
158
use std::fmt::Display;

use crossterm::event::{KeyCode as KC, KeyEvent as KE, KeyModifiers as KM};
use log::Level;
use tui::{
    style::{Color, Style},
    text::{Span, Spans},
};

use crate::{
    clashctl::model::{ConnectionsWithSpeed, Log, Proxies, Rules, Traffic, Version},
    components::MovableListItem,
    utils::AsColor,
    Error, Result,
};

#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Event {
    Quit,
    Input(InputEvent),
    Update(UpdateEvent),
    Diagnostic(DiagnosticEvent),
}

impl<'a> MovableListItem<'a> for Event {
    fn to_spans(&self) -> Spans<'a> {
        match self {
            Event::Quit => Spans(vec![]),
            Event::Update(event) => Spans(vec![
                Span::styled("⇵  ", Style::default().fg(Color::Yellow)),
                Span::raw(event.to_string()),
            ]),
            Event::Input(event) => Spans(vec![
                Span::styled("✜  ", Style::default().fg(Color::Green)),
                Span::raw(format!("{:?}", event)),
            ]),
            Event::Diagnostic(event) => match event {
                DiagnosticEvent::Log(level, payload) => Spans(vec![
                    Span::styled(
                        format!("✇  {:<6}", level),
                        Style::default().fg(level.as_color()),
                    ),
                    Span::raw(payload.to_owned()),
                ]),
            },
        }
    }
}

impl Event {
    pub fn is_quit(&self) -> bool {
        matches!(self, Event::Quit)
    }

    pub fn is_interface(&self) -> bool {
        matches!(self, Event::Input(_))
    }

    pub fn is_update(&self) -> bool {
        matches!(self, Event::Update(_))
    }

    pub fn is_diagnostic(&self) -> bool {
        matches!(self, Event::Diagnostic(_))
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum InputEvent {
    Esc,
    TabGoto(u8),
    ToggleDebug,
    ToggleHold,
    List(ListEvent),
    TestLatency,
    NextSort,
    PrevSort,
    Other(KE),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListEvent {
    pub fast: bool,
    pub code: KC,
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum UpdateEvent {
    Connection(ConnectionsWithSpeed),
    Version(Version),
    Traffic(Traffic),
    Proxies(Proxies),
    Rules(Rules),
    Log(Log),
    ProxyTestLatencyDone,
}

impl Display for UpdateEvent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            UpdateEvent::Connection(x) => write!(f, "{:?}", x),
            UpdateEvent::Version(x) => write!(f, "{:?}", x),
            UpdateEvent::Traffic(x) => write!(f, "{:?}", x),
            UpdateEvent::Proxies(x) => write!(f, "{:?}", x),
            UpdateEvent::Rules(x) => write!(f, "{:?}", x),
            UpdateEvent::Log(x) => write!(f, "{:?}", x),
            UpdateEvent::ProxyTestLatencyDone => write!(f, "Test latency done"),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DiagnosticEvent {
    Log(Level, String),
}

impl TryFrom<KC> for Event {
    type Error = Error;
    fn try_from(value: KC) -> Result<Self> {
        match value {
            KC::Char('q') | KC::Char('x') => Ok(Event::Quit),
            KC::Char('t') => Ok(Event::Input(InputEvent::TestLatency)),
            KC::Esc => Ok(Event::Input(InputEvent::Esc)),
            KC::Char(' ') => Ok(Event::Input(InputEvent::ToggleHold)),
            KC::Char(char) if char.is_ascii_digit() => Ok(Event::Input(InputEvent::TabGoto(
                char.to_digit(10)
                    .expect("char.is_ascii_digit() should be able to parse into number")
                    as u8,
            ))),
            _ => Err(Error::TuiInternalErr),
        }
    }
}

impl From<KE> for Event {
    fn from(value: KE) -> Self {
        match (value.modifiers, value.code) {
            (KM::CONTROL, KC::Char('c')) => Self::Quit,
            (KM::CONTROL, KC::Char('d')) => Self::Input(InputEvent::ToggleDebug),
            (modi, arrow @ (KC::Left | KC::Right | KC::Up | KC::Down | KC::Enter)) => {
                Event::Input(InputEvent::List(ListEvent {
                    fast: matches!(modi, KM::CONTROL | KM::SHIFT),
                    code: arrow,
                }))
            }
            (KM::ALT, KC::Char('s')) => Self::Input(InputEvent::PrevSort),
            (KM::NONE, KC::Char('s')) => Self::Input(InputEvent::NextSort),
            (KM::NONE, key_code) => key_code
                .try_into()
                .unwrap_or_else(|_| Self::Input(InputEvent::Other(value))),
            _ => Self::Input(InputEvent::Other(value)),
        }
    }
}