aboutsummaryrefslogtreecommitdiffstats
path: root/src/tui/app.rs
blob: 52fc85af01d66fe0096ecff363c9be5f6fa95118 (plain) (blame)
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
use ratatui::widgets::{ListState, ScrollbarState};

/// A single chat message in the log
#[derive(Clone)]
pub struct ChatLine {
    pub nick: String,
    pub text: String,
    pub is_system: bool,
    pub is_notice: bool,
}

/// All mutable state for the TUI
pub struct AppState {
    pub nick: String,
    pub channel: String,
    pub messages: Vec<ChatLine>,
    pub members: Vec<String>,
    pub input: String,
    pub cursor: usize,
    pub chat_scroll: usize,
    pub members_scroll: ScrollbarState,
    pub members_list_state: ListState,
    pub status: String,
    pub connected: bool,
}

impl AppState {
    pub fn new() -> Self {
        Self {
            nick: String::new(),
            channel: String::new(),
            messages: Vec::new(),
            members: Vec::new(),
            input: String::new(),
            cursor: 0,
            chat_scroll: 0,
            members_scroll: ScrollbarState::new(0),
            members_list_state: ListState::default(),
            status: "Set nick with /nick to connect.".into(),
            connected: false,
        }
    }

    pub fn push_message(&mut self, nick: &str, text: &str) {
        self.messages.push(ChatLine {
            nick: nick.to_string(),
            text: text.to_string(),
            is_system: false,
            is_notice: false,
        });
    }

    pub fn push_system(&mut self, text: &str) {
        self.messages.push(ChatLine {
            nick: String::new(),
            text: text.to_string(),
            is_system: true,
            is_notice: false,
        });
    }

    pub fn input_insert(&mut self, ch: char) {
        self.input.insert(self.cursor, ch);
        self.cursor += ch.len_utf8();
    }

    pub fn input_backspace(&mut self) {
        if self.cursor == 0 {
            return;
        }
        // Find the start of the previous character
        let prev = self.input[..self.cursor]
            .char_indices()
            .last()
            .map(|(i, _)| i)
            .unwrap_or(0);
        self.input.remove(prev);
        self.cursor = prev;
    }

    pub fn cursor_left(&mut self) {
        self.cursor = self.input[..self.cursor]
            .char_indices()
            .last()
            .map(|(i, _)| i)
            .unwrap_or(0);
    }

    pub fn cursor_right(&mut self) {
        if self.cursor < self.input.len() {
            let ch = self.input[self.cursor..].chars().next().unwrap();
            self.cursor += ch.len_utf8();
        }
    }

    pub fn scroll_up(&mut self) {
        self.chat_scroll = self.chat_scroll.saturating_add(1);
    }

    pub fn scroll_down(&mut self) {
        self.chat_scroll = self.chat_scroll.saturating_sub(1);
    }

    pub fn members_scroll_up(&mut self) {
        let pos = self.members_scroll.get_position().saturating_sub(5);
        self.members_scroll = self.members_scroll.position(pos);
        *self.members_list_state.offset_mut() = pos;
    }

    pub fn members_scroll_down(&mut self) {
        let max = self.members.len().saturating_sub(1);
        let pos = (self.members_scroll.get_position().saturating_add(5)).min(max);
        self.members_scroll = self.members_scroll.position(pos);
        *self.members_list_state.offset_mut() = pos;
    }

    pub fn take_input(&mut self) -> String {
        self.cursor = 0;
        std::mem::take(&mut self.input)
    }

    pub fn sort_members(&mut self) {
        self.members.sort_by(|a, b| {
            // Strip sigils for sorting (@, +, %)
            let a = a.trim_start_matches(&['@', '+', '%', '~', '&'][..]);
            let b = b.trim_start_matches(&['@', '+', '%', '~', '&'][..]);
            a.to_lowercase().cmp(&b.to_lowercase())
        });
        self.members.dedup();
    }
}