Skip to main content

par_term/
help_ui.rs

1use crate::config::Config;
2use crate::ui_constants::{HELP_WINDOW_DEFAULT_HEIGHT, HELP_WINDOW_DEFAULT_WIDTH};
3use egui::{Color32, Context, Frame, RichText, Window, epaint::Shadow};
4use std::cell::Cell;
5
6/// Help UI manager using egui
7pub struct HelpUI {
8    /// Whether the help window is currently visible
9    pub visible: bool,
10}
11
12impl HelpUI {
13    /// Create a new help UI
14    pub fn new() -> Self {
15        Self { visible: false }
16    }
17
18    /// Toggle help window visibility
19    pub fn toggle(&mut self) {
20        self.visible = !self.visible;
21    }
22
23    /// Show the help window
24    pub fn show(&mut self, ctx: &Context) {
25        if !self.visible {
26            return;
27        }
28
29        // Ensure help panel is fully opaque regardless of terminal opacity
30        let mut style = (*ctx.global_style()).clone();
31        let solid_bg = Color32::from_rgba_unmultiplied(24, 24, 24, 255);
32        style.visuals.window_fill = solid_bg;
33        style.visuals.panel_fill = solid_bg;
34        style.visuals.widgets.noninteractive.bg_fill = solid_bg;
35        ctx.set_global_style(style);
36
37        let mut open = true;
38        let close_requested = Cell::new(false);
39
40        let viewport = ctx.input(|i| i.viewport_rect());
41        Window::new("Help")
42            .resizable(true)
43            .default_width(HELP_WINDOW_DEFAULT_WIDTH)
44            .default_height(HELP_WINDOW_DEFAULT_HEIGHT)
45            .default_pos(viewport.center())
46            .pivot(egui::Align2::CENTER_CENTER)
47            .open(&mut open)
48            .frame(
49                Frame::window(&ctx.global_style())
50                    .fill(solid_bg)
51                    .stroke(egui::Stroke::NONE)
52                    .shadow(Shadow {
53                        offset: [0, 0],
54                        blur: 0,
55                        spread: 0,
56                        color: Color32::TRANSPARENT,
57                    }),
58            )
59            .show(ctx, |ui| {
60                egui::ScrollArea::vertical().show(ui, |ui| {
61                    // About Section
62                    ui.heading("About par-term");
63                    ui.separator();
64
65                    ui.horizontal(|ui| {
66                        ui.label("Version:");
67                        ui.label(RichText::new(env!("CARGO_PKG_VERSION")).strong());
68                    });
69
70                    ui.add_space(4.0);
71                    ui.label(env!("CARGO_PKG_DESCRIPTION"));
72
73                    ui.add_space(4.0);
74                    ui.horizontal(|ui| {
75                        ui.label("Author:");
76                        ui.label(env!("CARGO_PKG_AUTHORS"));
77                    });
78
79                    ui.horizontal(|ui| {
80                        ui.label("License:");
81                        ui.label(env!("CARGO_PKG_LICENSE"));
82                    });
83
84                    ui.horizontal(|ui| {
85                        ui.label("Repository:");
86                        ui.hyperlink_to(
87                            env!("CARGO_PKG_REPOSITORY"),
88                            env!("CARGO_PKG_REPOSITORY"),
89                        );
90                    });
91
92                    ui.add_space(12.0);
93
94                    // Configuration Paths Section
95                    ui.heading("Configuration Paths");
96                    ui.separator();
97
98                    let config_path = Config::config_path();
99                    let shaders_dir = Config::shaders_dir();
100
101                    ui.horizontal(|ui| {
102                        ui.label("Config file:");
103                        ui.label(RichText::new(config_path.display().to_string()).monospace());
104                    });
105
106                    ui.horizontal(|ui| {
107                        ui.label("Shaders folder:");
108                        ui.label(RichText::new(shaders_dir.display().to_string()).monospace());
109                    });
110
111                    ui.add_space(12.0);
112
113                    // Keyboard Shortcuts Section
114                    ui.heading("Keyboard Shortcuts");
115                    ui.separator();
116
117                    // Use a grid for clean alignment
118                    egui::Grid::new("shortcuts_grid")
119                        .num_columns(2)
120                        .spacing([20.0, 4.0])
121                        .striped(true)
122                        .show(ui, |ui| {
123                            // Navigation
124                            ui.label(RichText::new("Navigation").strong().underline());
125                            ui.end_row();
126
127                            shortcut_row(ui, "PageUp", "Scroll up one page");
128                            shortcut_row(ui, "PageDown", "Scroll down one page");
129                            shortcut_row(ui, "Shift+Home", "Scroll to top");
130                            shortcut_row(ui, "Shift+End", "Scroll to bottom");
131                            shortcut_row(ui, "Mouse wheel", "Scroll up/down");
132
133                            ui.end_row();
134
135                            // Window & Display
136                            ui.label(RichText::new("Window & Display").strong().underline());
137                            ui.end_row();
138
139                            shortcut_row(ui, "F1", "Toggle this help panel");
140                            shortcut_row(ui, "F3", "Toggle FPS overlay");
141                            shortcut_row(ui, "F5", "Reload configuration");
142                            shortcut_row(ui, "F11", "Toggle fullscreen / Shader editor");
143                            shortcut_row(ui, "F12", "Toggle settings panel");
144
145                            ui.end_row();
146
147                            // Font & Text
148                            ui.label(RichText::new("Font & Text").strong().underline());
149                            ui.end_row();
150
151                            shortcut_row(ui, "Ctrl++", "Increase font size");
152                            shortcut_row(ui, "Ctrl+-", "Decrease font size");
153                            shortcut_row(ui, "Ctrl+0", "Reset font size to default");
154
155                            ui.end_row();
156
157                            // Selection & Clipboard
158                            ui.label(RichText::new("Selection & Clipboard").strong().underline());
159                            ui.end_row();
160
161                            shortcut_row(ui, "Click + Drag", "Select text");
162                            shortcut_row(ui, "Double-click", "Select word");
163                            shortcut_row(ui, "Triple-click", "Select line");
164                            shortcut_row(ui, "Ctrl+Shift+C", "Copy selection");
165                            shortcut_row(ui, "Ctrl+Shift+V", "Paste from clipboard");
166                            shortcut_row(ui, "Ctrl+Shift+H", "Toggle clipboard history");
167                            shortcut_row(ui, "Cmd/Ctrl+R", "Fuzzy command history search");
168                            shortcut_row(ui, "Middle-click", "Paste (if enabled)");
169
170                            ui.end_row();
171
172                            // Search
173                            ui.label(RichText::new("Search").strong().underline());
174                            ui.end_row();
175
176                            shortcut_row(ui, "Cmd/Ctrl+F", "Open search");
177                            shortcut_row(ui, "Enter", "Find next match");
178                            shortcut_row(ui, "Shift+Enter", "Find previous match");
179                            shortcut_row(ui, "Escape", "Close search");
180
181                            ui.end_row();
182
183                            // Terminal
184                            ui.label(RichText::new("Terminal").strong().underline());
185                            ui.end_row();
186
187                            shortcut_row(ui, "Ctrl+L", "Clear screen");
188                            shortcut_row(ui, "Ctrl+Shift+S", "Take screenshot");
189                            shortcut_row(ui, "Ctrl+Shift+R", "Toggle session recording");
190                            shortcut_row(ui, "Ctrl+Shift+F5", "Fix rendering (after monitor change)");
191
192                            ui.end_row();
193
194                            // URL Handling
195                            ui.label(RichText::new("URL Handling").strong().underline());
196                            ui.end_row();
197
198                            shortcut_row(ui, "Ctrl+Click URL", "Open URL in browser");
199                        });
200
201                    ui.add_space(12.0);
202
203                    // Copy Mode Section
204                    ui.heading("Copy Mode (Vi-Style)");
205                    ui.separator();
206
207                    ui.label("Copy Mode provides keyboard-driven text selection and navigation through the terminal buffer, including scrollback history.");
208
209                    ui.add_space(4.0);
210
211                    egui::Grid::new("copy_mode_grid")
212                        .num_columns(2)
213                        .spacing([20.0, 4.0])
214                        .striped(true)
215                        .show(ui, |ui| {
216                            ui.label(RichText::new("Enter / Exit").strong().underline());
217                            ui.end_row();
218
219                            #[cfg(target_os = "macos")]
220                            shortcut_row(ui, "Cmd+Shift+C", "Toggle copy mode");
221                            #[cfg(not(target_os = "macos"))]
222                            shortcut_row(ui, "Ctrl+Shift+Space", "Toggle copy mode");
223                            shortcut_row(ui, "q / Escape", "Exit copy mode");
224
225                            ui.end_row();
226
227                            ui.label(RichText::new("Navigation").strong().underline());
228                            ui.end_row();
229
230                            shortcut_row(ui, "h j k l", "Left / Down / Up / Right");
231                            shortcut_row(ui, "w / b / e", "Word forward / back / end");
232                            shortcut_row(ui, "W / B / E", "WORD forward / back / end");
233                            shortcut_row(ui, "0", "Start of line");
234                            shortcut_row(ui, "$", "End of line");
235                            shortcut_row(ui, "^", "First non-blank character");
236                            shortcut_row(ui, "gg", "Top of scrollback");
237                            shortcut_row(ui, "G", "Bottom of buffer");
238                            shortcut_row(ui, "Ctrl+U / Ctrl+D", "Half page up / down");
239                            shortcut_row(ui, "Ctrl+B / Ctrl+F", "Full page up / down");
240
241                            ui.end_row();
242
243                            ui.label(RichText::new("Selection & Yank").strong().underline());
244                            ui.end_row();
245
246                            shortcut_row(ui, "v", "Character selection");
247                            shortcut_row(ui, "V", "Line selection");
248                            shortcut_row(ui, "y", "Yank (copy) selection to clipboard");
249                            shortcut_row(ui, "1-9", "Count prefix (e.g. 5j = down 5 lines)");
250
251                            ui.end_row();
252
253                            ui.label(RichText::new("Search").strong().underline());
254                            ui.end_row();
255
256                            shortcut_row(ui, "/", "Search forward");
257                            shortcut_row(ui, "?", "Search backward");
258                            shortcut_row(ui, "n", "Next match");
259                            shortcut_row(ui, "N", "Previous match");
260
261                            ui.end_row();
262
263                            ui.label(RichText::new("Marks").strong().underline());
264                            ui.end_row();
265
266                            shortcut_row(ui, "m + char", "Set mark at current position");
267                            shortcut_row(ui, "' + char", "Jump to mark");
268                        });
269
270                    ui.add_space(12.0);
271
272                    // Mouse Actions Section
273                    ui.heading("Mouse Actions");
274                    ui.separator();
275
276                    egui::Grid::new("mouse_grid")
277                        .num_columns(2)
278                        .spacing([20.0, 4.0])
279                        .striped(true)
280                        .show(ui, |ui| {
281                            shortcut_row(ui, "Scrollbar drag", "Scroll through history");
282                            shortcut_row(ui, "Scrollbar click", "Jump to position");
283                        });
284
285                    ui.add_space(12.0);
286
287                    // Tips Section
288                    ui.heading("Tips");
289                    ui.separator();
290
291                    ui.label("• Configuration changes made via F12 settings are saved to the config file.");
292                    ui.label("• Press F5 to reload config without restarting the terminal.");
293                    ui.label("• Custom shaders can be placed in the shaders folder.");
294                    ui.label("• The shader editor (F11) allows live editing when a shader is configured.");
295                    ui.label("• If display looks corrupted after moving between monitors, press Ctrl+Shift+F5.");
296
297                    ui.add_space(12.0);
298
299                    // Close button
300                    ui.separator();
301                    ui.horizontal(|ui| {
302                        if ui.button("Close").clicked() {
303                            close_requested.set(true);
304                        }
305                        ui.label(RichText::new("Press F1 or Escape to close").weak());
306                    });
307                });
308            });
309
310        // Update visibility based on window state
311        if !open || close_requested.get() {
312            self.visible = false;
313        }
314    }
315}
316
317impl Default for HelpUI {
318    fn default() -> Self {
319        Self::new()
320    }
321}
322
323/// Helper function to add a shortcut row to the grid
324fn shortcut_row(ui: &mut egui::Ui, shortcut: &str, description: &str) {
325    ui.label(RichText::new(shortcut).monospace().strong());
326    ui.label(description);
327    ui.end_row();
328}