television_screen/
keybindings.rs

1use rustc_hash::FxHashMap;
2use std::fmt::Display;
3
4use crate::{colors::Colorscheme, mode::Mode};
5use ratatui::{
6    layout::Constraint,
7    style::{Color, Style},
8    text::{Line, Span},
9    widgets::{Cell, Row, Table},
10};
11
12#[derive(Debug, Clone)]
13pub struct DisplayableKeybindings {
14    bindings: FxHashMap<DisplayableAction, Vec<String>>,
15}
16
17impl DisplayableKeybindings {
18    pub fn new(bindings: FxHashMap<DisplayableAction, Vec<String>>) -> Self {
19        Self { bindings }
20    }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub enum DisplayableAction {
25    ResultsNavigation,
26    PreviewNavigation,
27    SelectEntry,
28    CopyEntryToClipboard,
29    SendToChannel,
30    ToggleRemoteControl,
31    Cancel,
32    Quit,
33    ToggleHelpBar,
34}
35
36impl Display for DisplayableAction {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        let action = match self {
39            DisplayableAction::ResultsNavigation => "Results navigation",
40            DisplayableAction::PreviewNavigation => "Preview navigation",
41            DisplayableAction::SelectEntry => "Select entry",
42            DisplayableAction::CopyEntryToClipboard => {
43                "Copy entry to clipboard"
44            }
45            DisplayableAction::SendToChannel => "Send to channel",
46            DisplayableAction::ToggleRemoteControl => "Toggle Remote control",
47            DisplayableAction::Cancel => "Cancel",
48            DisplayableAction::Quit => "Quit",
49            DisplayableAction::ToggleHelpBar => "Toggle help bar",
50        };
51        write!(f, "{action}")
52    }
53}
54
55pub fn build_keybindings_table<'a>(
56    keybindings: &'a FxHashMap<Mode, DisplayableKeybindings>,
57    mode: Mode,
58    colorscheme: &'a Colorscheme,
59) -> Table<'a> {
60    match mode {
61        Mode::Channel => build_keybindings_table_for_channel(
62            &keybindings[&mode],
63            colorscheme,
64        ),
65        Mode::RemoteControl => build_keybindings_table_for_channel_selection(
66            &keybindings[&mode],
67            colorscheme,
68        ),
69        Mode::SendToChannel => {
70            build_keybindings_table_for_channel_transitions(
71                &keybindings[&mode],
72                colorscheme,
73            )
74        }
75    }
76}
77
78fn build_keybindings_table_for_channel<'a>(
79    keybindings: &'a DisplayableKeybindings,
80    colorscheme: &'a Colorscheme,
81) -> Table<'a> {
82    // Results navigation
83    let results_navigation_keys = keybindings
84        .bindings
85        .get(&DisplayableAction::ResultsNavigation)
86        .unwrap();
87    let results_row = Row::new(build_cells_for_group(
88        "Results navigation",
89        results_navigation_keys,
90        colorscheme.help.metadata_field_name_fg,
91        colorscheme.mode.channel,
92    ));
93
94    // Preview navigation
95    let preview_navigation_keys = keybindings
96        .bindings
97        .get(&DisplayableAction::PreviewNavigation)
98        .unwrap();
99    let preview_row = Row::new(build_cells_for_group(
100        "Preview navigation",
101        preview_navigation_keys,
102        colorscheme.help.metadata_field_name_fg,
103        colorscheme.mode.channel,
104    ));
105
106    // Select entry
107    let select_entry_keys = keybindings
108        .bindings
109        .get(&DisplayableAction::SelectEntry)
110        .unwrap();
111    let select_entry_row = Row::new(build_cells_for_group(
112        "Select entry",
113        select_entry_keys,
114        colorscheme.help.metadata_field_name_fg,
115        colorscheme.mode.channel,
116    ));
117
118    // Copy entry to clipboard
119    let copy_entry_keys = keybindings
120        .bindings
121        .get(&DisplayableAction::CopyEntryToClipboard)
122        .unwrap();
123    let copy_entry_row = Row::new(build_cells_for_group(
124        "Copy entry to clipboard",
125        copy_entry_keys,
126        colorscheme.help.metadata_field_name_fg,
127        colorscheme.mode.channel,
128    ));
129
130    // Send to channel
131    let send_to_channel_keys = keybindings
132        .bindings
133        .get(&DisplayableAction::SendToChannel)
134        .unwrap();
135    let send_to_channel_row = Row::new(build_cells_for_group(
136        "Send results to",
137        send_to_channel_keys,
138        colorscheme.help.metadata_field_name_fg,
139        colorscheme.mode.channel,
140    ));
141
142    // Switch channels
143    let switch_channels_keys = keybindings
144        .bindings
145        .get(&DisplayableAction::ToggleRemoteControl)
146        .unwrap();
147    let switch_channels_row = Row::new(build_cells_for_group(
148        "Toggle Remote control",
149        switch_channels_keys,
150        colorscheme.help.metadata_field_name_fg,
151        colorscheme.mode.channel,
152    ));
153
154    let widths = vec![Constraint::Fill(1), Constraint::Fill(2)];
155
156    Table::new(
157        vec![
158            results_row,
159            preview_row,
160            select_entry_row,
161            copy_entry_row,
162            send_to_channel_row,
163            switch_channels_row,
164        ],
165        widths,
166    )
167}
168
169fn build_keybindings_table_for_channel_selection<'a>(
170    keybindings: &'a DisplayableKeybindings,
171    colorscheme: &'a Colorscheme,
172) -> Table<'a> {
173    // Results navigation
174    let navigation_keys = keybindings
175        .bindings
176        .get(&DisplayableAction::ResultsNavigation)
177        .unwrap();
178    let results_row = Row::new(build_cells_for_group(
179        "Browse channels",
180        navigation_keys,
181        colorscheme.help.metadata_field_name_fg,
182        colorscheme.mode.remote_control,
183    ));
184
185    // Select entry
186    let select_entry_keys = keybindings
187        .bindings
188        .get(&DisplayableAction::SelectEntry)
189        .unwrap();
190    let select_entry_row = Row::new(build_cells_for_group(
191        "Select channel",
192        select_entry_keys,
193        colorscheme.help.metadata_field_name_fg,
194        colorscheme.mode.remote_control,
195    ));
196
197    // Remote control
198    let switch_channels_keys = keybindings
199        .bindings
200        .get(&DisplayableAction::ToggleRemoteControl)
201        .unwrap();
202    let switch_channels_row = Row::new(build_cells_for_group(
203        "Toggle Remote control",
204        switch_channels_keys,
205        colorscheme.help.metadata_field_name_fg,
206        colorscheme.mode.remote_control,
207    ));
208
209    Table::new(
210        vec![results_row, select_entry_row, switch_channels_row],
211        vec![Constraint::Fill(1), Constraint::Fill(2)],
212    )
213}
214
215fn build_keybindings_table_for_channel_transitions<'a>(
216    keybindings: &'a DisplayableKeybindings,
217    colorscheme: &'a Colorscheme,
218) -> Table<'a> {
219    // Results navigation
220    let results_navigation_keys = keybindings
221        .bindings
222        .get(&DisplayableAction::ResultsNavigation)
223        .unwrap();
224    let results_row = Row::new(build_cells_for_group(
225        "Browse channels",
226        results_navigation_keys,
227        colorscheme.help.metadata_field_name_fg,
228        colorscheme.mode.send_to_channel,
229    ));
230
231    // Select entry
232    let select_entry_keys = keybindings
233        .bindings
234        .get(&DisplayableAction::SelectEntry)
235        .unwrap();
236    let select_entry_row = Row::new(build_cells_for_group(
237        "Send to channel",
238        select_entry_keys,
239        colorscheme.help.metadata_field_name_fg,
240        colorscheme.mode.send_to_channel,
241    ));
242
243    // Cancel
244    let cancel_keys = keybindings
245        .bindings
246        .get(&DisplayableAction::Cancel)
247        .unwrap();
248    let cancel_row = Row::new(build_cells_for_group(
249        "Cancel",
250        cancel_keys,
251        colorscheme.help.metadata_field_name_fg,
252        colorscheme.mode.send_to_channel,
253    ));
254
255    Table::new(
256        vec![results_row, select_entry_row, cancel_row],
257        vec![Constraint::Fill(1), Constraint::Fill(2)],
258    )
259}
260
261fn build_cells_for_group<'a>(
262    group_name: &str,
263    keys: &'a [String],
264    key_color: Color,
265    value_color: Color,
266) -> Vec<Cell<'a>> {
267    // group name
268    let mut cells = vec![Cell::from(Span::styled(
269        group_name.to_owned() + ": ",
270        Style::default().fg(key_color),
271    ))];
272
273    let spans = keys.iter().skip(1).fold(
274        vec![Span::styled(
275            keys[0].clone(),
276            Style::default().fg(value_color),
277        )],
278        |mut acc, key| {
279            acc.push(Span::raw(" / "));
280            acc.push(Span::styled(
281                key.to_owned(),
282                Style::default().fg(value_color),
283            ));
284            acc
285        },
286    );
287
288    cells.push(Cell::from(Line::from(spans)));
289
290    cells
291}