diff options
Diffstat (limited to '')
| -rw-r--r-- | src/ui.rs | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..b9abeff --- /dev/null +++ b/src/ui.rs | |||
| @@ -0,0 +1,221 @@ | |||
| 1 | use crate::keymap; | ||
| 2 | use eframe::egui::{self, Color32, CornerRadius, Stroke, Vec2}; | ||
| 3 | |||
| 4 | const LEVEL_NAMES: &[&str; 4] = &[ | ||
| 5 | "Level 1 (base)", | ||
| 6 | "Level 2 (Shift)", | ||
| 7 | "Level 3 (AltGr)", | ||
| 8 | "Level 4 (Shift+AltGr)", | ||
| 9 | ]; | ||
| 10 | |||
| 11 | const KEY_BG: Color32 = Color32::from_rgb(0x33, 0x33, 0x33); | ||
| 12 | const KEY_BG_MOD_SHIFT: Color32 = Color32::from_rgb(0x2a, 0x5a, 0x2a); | ||
| 13 | const KEY_BG_MOD_L3: Color32 = Color32::from_rgb(0x4a, 0x2a, 0x5a); | ||
| 14 | const KEY_BG_MOD_OTHER: Color32 = Color32::from_rgb(0x3a, 0x3a, 0x3a); | ||
| 15 | const KEY_TEXT: Color32 = Color32::from_rgb(0xcc, 0xcc, 0xcc); | ||
| 16 | const KEY_TEXT_DIM: Color32 = Color32::from_rgb(0x55, 0x55, 0x55); | ||
| 17 | const KEY_TEXT_ACTIVE: Color32 = Color32::from_rgb(0xff, 0xff, 0xff); | ||
| 18 | const KEY_TEXT_MOD: Color32 = Color32::from_rgb(0xff, 0xff, 0xcc); | ||
| 19 | const KEY_TEXT_DEAD: Color32 = Color32::from_rgb(0xff, 0xaa, 0x44); | ||
| 20 | |||
| 21 | const KEY_SPACING: f32 = 4.0; | ||
| 22 | const KEY_UNIT: f32 = 52.0; | ||
| 23 | const KEY_HEIGHT: f32 = 48.0; | ||
| 24 | |||
| 25 | fn key_bg(kind: &keymap::KeyKind) -> Color32 { | ||
| 26 | match kind { | ||
| 27 | keymap::KeyKind::Shift => KEY_BG_MOD_SHIFT, | ||
| 28 | keymap::KeyKind::Level3 => KEY_BG_MOD_L3, | ||
| 29 | keymap::KeyKind::Lock | keymap::KeyKind::Control | keymap::KeyKind::Alt | keymap::KeyKind::Super => { | ||
| 30 | KEY_BG_MOD_OTHER | ||
| 31 | } | ||
| 32 | keymap::KeyKind::Normal => KEY_BG, | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | fn text_color_for_level(level: usize, active_level: usize, kind: &keymap::KeyKind) -> Color32 { | ||
| 37 | if level == active_level { | ||
| 38 | match kind { | ||
| 39 | keymap::KeyKind::Shift | keymap::KeyKind::Level3 => KEY_TEXT_MOD, | ||
| 40 | _ => KEY_TEXT_ACTIVE, | ||
| 41 | } | ||
| 42 | } else { | ||
| 43 | KEY_TEXT_DIM | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | pub fn render( | ||
| 48 | ui: &mut egui::Ui, | ||
| 49 | key_data: &Option<keymap::KeyData>, | ||
| 50 | selected_level: &mut usize, | ||
| 51 | active_level: usize, | ||
| 52 | active_group: usize, | ||
| 53 | error: &Option<String>, | ||
| 54 | ) { | ||
| 55 | egui::Panel::top("toolbar").show_inside(ui, |ui| { | ||
| 56 | ui.horizontal(|ui| { | ||
| 57 | ui.label(egui::RichText::new("xkbpreview").strong()); | ||
| 58 | |||
| 59 | if let Some(data) = key_data { | ||
| 60 | let g = active_group.min(data.layouts.len().saturating_sub(1)); | ||
| 61 | ui.label(format!(" layout: {}", data.layouts[g])); | ||
| 62 | if data.is_iso { | ||
| 63 | ui.label(" [ISO]"); | ||
| 64 | } else { | ||
| 65 | ui.label(" [ANSI]"); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | ui.label(format!(" Lv:{}", active_level)); | ||
| 70 | |||
| 71 | ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { | ||
| 72 | ui.label("Highlight:"); | ||
| 73 | for (i, name) in LEVEL_NAMES.iter().enumerate() { | ||
| 74 | let mark = if i == active_level { "[x]" } else { "[ ]" }; | ||
| 75 | if ui.selectable_label(*selected_level == i, format!("{} {}", mark, name)).clicked() { | ||
| 76 | *selected_level = i; | ||
| 77 | } | ||
| 78 | } | ||
| 79 | }); | ||
| 80 | }); | ||
| 81 | }); | ||
| 82 | |||
| 83 | if let Some(err) = error { | ||
| 84 | egui::CentralPanel::default().show_inside(ui, |ui| { | ||
| 85 | ui.colored_label(Color32::RED, format!("Error: {}", err)); | ||
| 86 | ui.label("Make sure you're running under X11 and the XKB configuration is available."); | ||
| 87 | }); | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | |||
| 91 | let data = match key_data { | ||
| 92 | Some(d) => d, | ||
| 93 | None => { | ||
| 94 | egui::CentralPanel::default().show_inside(ui, |ui| { | ||
| 95 | ui.label("Loading keyboard layout..."); | ||
| 96 | }); | ||
| 97 | return; | ||
| 98 | } | ||
| 99 | }; | ||
| 100 | |||
| 101 | egui::CentralPanel::default().show_inside(ui, |ui| { | ||
| 102 | ui.vertical_centered(|ui| { | ||
| 103 | let rows = group_by_row(&data.keys); | ||
| 104 | let max_row = rows.iter().map(|(r, _)| *r).max().unwrap_or(0); | ||
| 105 | let total_w = 15.5 * (KEY_UNIT + KEY_SPACING); | ||
| 106 | |||
| 107 | let (rect, _) = ui.allocate_exact_size( | ||
| 108 | Vec2::new(total_w, (max_row + 1) as f32 * (KEY_HEIGHT + KEY_SPACING)), | ||
| 109 | egui::Sense::hover(), | ||
| 110 | ); | ||
| 111 | |||
| 112 | let painter = ui.painter_at(rect); | ||
| 113 | let origin = rect.min; | ||
| 114 | |||
| 115 | for (row_idx, entries) in &rows { | ||
| 116 | for entry in entries { | ||
| 117 | let p = &entry.pos; | ||
| 118 | let x = p.col * (KEY_UNIT + KEY_SPACING); | ||
| 119 | let y = *row_idx as f32 * (KEY_HEIGHT + KEY_SPACING); | ||
| 120 | let w = p.width * KEY_UNIT + (p.width - 1.0) * KEY_SPACING; | ||
| 121 | |||
| 122 | let key_rect = egui::Rect::from_min_size( | ||
| 123 | origin + Vec2::new(x, y), | ||
| 124 | Vec2::new(w, KEY_HEIGHT), | ||
| 125 | ); | ||
| 126 | |||
| 127 | let bg = key_bg(&entry.kind); | ||
| 128 | painter.rect_filled(key_rect, CornerRadius::same(3), bg); | ||
| 129 | painter.rect_stroke(key_rect, CornerRadius::same(3), Stroke::new(1.0, Color32::from_rgb(0x55, 0x55, 0x55)), egui::StrokeKind::Middle); | ||
| 130 | |||
| 131 | let inset = 3.0; | ||
| 132 | let cw = key_rect.width() / 2.0; | ||
| 133 | |||
| 134 | let centers: [egui::Pos2; 4] = [ | ||
| 135 | key_rect.center(), | ||
| 136 | egui::Pos2::new(key_rect.left() + cw / 2.0, key_rect.top() + inset + 4.0), | ||
| 137 | egui::Pos2::new(key_rect.right() - cw / 2.0, key_rect.top() + inset + 4.0), | ||
| 138 | egui::Pos2::new(key_rect.left() + cw / 2.0, key_rect.bottom() - inset - 4.0), | ||
| 139 | ]; | ||
| 140 | |||
| 141 | let level_order = [0usize, 1, 2, 3]; | ||
| 142 | let font_sizes = [15.0, 10.0, 10.0, 9.0]; | ||
| 143 | let aligns = [ | ||
| 144 | egui::Align2::CENTER_CENTER, | ||
| 145 | egui::Align2::LEFT_TOP, | ||
| 146 | egui::Align2::RIGHT_TOP, | ||
| 147 | egui::Align2::LEFT_BOTTOM, | ||
| 148 | ]; | ||
| 149 | |||
| 150 | let g = active_group.min(3); | ||
| 151 | for &li in &level_order { | ||
| 152 | let label = &entry.labels[g][li]; | ||
| 153 | if label.is_empty() { | ||
| 154 | continue; | ||
| 155 | } | ||
| 156 | let color = if entry.dead[g][li] { | ||
| 157 | KEY_TEXT_DEAD | ||
| 158 | } else if li == 0 { | ||
| 159 | if 0 == active_level { | ||
| 160 | text_color_for_level(li, active_level, &entry.kind) | ||
| 161 | } else { | ||
| 162 | KEY_TEXT | ||
| 163 | } | ||
| 164 | } else { | ||
| 165 | text_color_for_level(li, active_level, &entry.kind) | ||
| 166 | }; | ||
| 167 | let font = if label.len() > 2 { | ||
| 168 | egui::FontId::proportional(font_sizes[li] - 1.0) | ||
| 169 | } else { | ||
| 170 | egui::FontId::proportional(font_sizes[li]) | ||
| 171 | }; | ||
| 172 | painter.text(centers[li], aligns[li], label, font, color); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | let legend_y = (max_row + 1) as f32 * (KEY_HEIGHT + KEY_SPACING) + 16.0; | ||
| 178 | let legend_items = [ | ||
| 179 | (KEY_BG_MOD_SHIFT, "Shift (triggers L2)"), | ||
| 180 | (KEY_BG_MOD_L3, "AltGr (triggers L3)"), | ||
| 181 | (KEY_BG_MOD_OTHER, "Other modifiers"), | ||
| 182 | (KEY_TEXT_DEAD, "Dead key accent"), | ||
| 183 | ]; | ||
| 184 | for (i, (color, text)) in legend_items.iter().enumerate() { | ||
| 185 | let ly = legend_y + i as f32 * 20.0; | ||
| 186 | let r = egui::Rect::from_min_size( | ||
| 187 | origin + Vec2::new(0.0, ly), | ||
| 188 | Vec2::new(14.0, 14.0), | ||
| 189 | ); | ||
| 190 | painter.rect_filled(r, CornerRadius::same(2), *color); | ||
| 191 | painter.text( | ||
| 192 | egui::Pos2::new(r.right() + 6.0, r.center().y), | ||
| 193 | egui::Align2::LEFT_CENTER, | ||
| 194 | *text, | ||
| 195 | egui::FontId::proportional(12.0), | ||
| 196 | KEY_TEXT, | ||
| 197 | ); | ||
| 198 | } | ||
| 199 | }); | ||
| 200 | }); | ||
| 201 | } | ||
| 202 | |||
| 203 | fn group_by_row<'a>(keys: &'a [keymap::KeyEntry]) -> Vec<(usize, Vec<&'a keymap::KeyEntry>)> { | ||
| 204 | let mut rows: Vec<(usize, Vec<&'a keymap::KeyEntry>)> = Vec::new(); | ||
| 205 | for key in keys { | ||
| 206 | let row = key.pos.row; | ||
| 207 | if row > 4 { | ||
| 208 | continue; | ||
| 209 | } | ||
| 210 | if let Some(entry) = rows.iter_mut().find(|(r, _)| *r == row) { | ||
| 211 | entry.1.push(key); | ||
| 212 | } else { | ||
| 213 | rows.push((row, vec![key])); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | rows.sort_by_key(|(r, _)| *r); | ||
| 217 | for (_, entries) in &mut rows { | ||
| 218 | entries.sort_by(|a, b| a.pos.col.partial_cmp(&b.pos.col).unwrap()); | ||
| 219 | } | ||
| 220 | rows | ||
| 221 | } | ||
