| ... | ... | @@ -0,0 +1,969 @@ |
| 1 | //! Insert Symbol, as OneNote 2010's: a gallery of the symbols inserted lately, and More |
| 2 | //! Symbols, which finds any character by subset, name or code. |
| 3 | |
| 4 | use crate::{Command, State, art, commands::Choice}; |
| 5 | use std::sync::OnceLock; |
| 6 | use ui::{Anchor, Axis, Flags, Id, Rows, Spec, Ui, children, fill, fit, popup::Item, px}; |
| 7 | use winit::keyboard::NamedKey; |
| 8 | |
| 9 | const TITLE: &str = "Symbol"; |
| 10 | /// Symbols the gallery holds and the settings keep. |
| 11 | pub const GALLERY: usize = 20; |
| 12 | /// The dialog's grid: 16 columns of cells, as OneNote's, with a scrollbar beside them. |
| 13 | const COLUMNS: usize = 16; |
| 14 | const ROWS: usize = 6; |
| 15 | const CELL: f32 = 30.0; |
| 16 | /// The grid's width, with room for its scrollbar. |
| 17 | const GRID: f32 = COLUMNS as f32 * CELL + 12.0; |
| 18 | |
| 19 | /// OneNote 2010's gallery before any symbol is inserted (Windows 7 lab, 2026-10-01). |
| 20 | const DEFAULTS: [char; GALLERY] = [ |
| 21 | '€', '£', '¥', '©', '®', '™', '±', '≠', '≤', '≥', '÷', '×', '∞', 'µ', 'α', 'β', 'π', 'Ω', '∑', |
| 22 | '☺', |
| 23 | ]; |
| 24 | |
| 25 | /// More Symbols while it is open. |
| 26 | pub struct Symbols { |
| 27 | search: String, |
| 28 | /// The search, upper case, and the characters it found; none while it is empty. |
| 29 | found: Option<(String, Vec<char>)>, |
| 30 | selected: char, |
| 31 | /// What the Character code field holds, a hex code point. |
| 32 | code: String, |
| 33 | /// Cancel reads Close once a symbol is inserted, as in OneNote. |
| 34 | inserted: bool, |
| 35 | } |
| 36 | |
| 37 | impl Symbols { |
| 38 | fn select(&mut self, symbol: char) { |
| 39 | self.selected = symbol; |
| 40 | self.code = code(symbol); |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | /// `symbol`'s code point in hex, as the Character code field shows it. |
| 45 | fn code(symbol: char) -> String { |
| 46 | format!("{:04X}", u32::from(symbol)) |
| 47 | } |
| 48 | |
| 49 | fn id() -> Id { |
| 50 | Id::ROOT.child("symbols") |
| 51 | } |
| 52 | |
| 53 | fn search_field() -> Id { |
| 54 | id().child("search") |
| 55 | } |
| 56 | |
| 57 | fn code_field() -> Id { |
| 58 | id().child("code") |
| 59 | } |
| 60 | |
| 61 | fn grid() -> Id { |
| 62 | id().child("grid") |
| 63 | } |
| 64 | |
| 65 | fn subsets() -> Id { |
| 66 | id().child("subsets") |
| 67 | } |
| 68 | |
| 69 | /// The gallery's symbols: those inserted lately, then OneNote's defaults. |
| 70 | pub fn recent(inserted: &[char]) -> Vec<char> { |
| 71 | let mut shown = inserted.to_vec(); |
| 72 | shown.extend(DEFAULTS.iter().filter(|symbol| !inserted.contains(symbol))); |
| 73 | shown.truncate(GALLERY); |
| 74 | shown |
| 75 | } |
| 76 | |
| 77 | /// Builds popup `id` below `anchor` while it is open as the toolbar's Symbol gallery: |
| 78 | /// `symbols`, five to a row, then More Symbols. Returns the symbol chosen, or `None` for |
| 79 | /// More Symbols. |
| 80 | pub fn gallery(ui: &mut Ui, id: Id, anchor: Anchor, symbols: &[char]) -> Option<Option<char>> { |
| 81 | const SIZE: f32 = 32.0; |
| 82 | let groups = [ |
| 83 | ui::popup::Group { |
| 84 | heading: "", |
| 85 | cells: symbols.len(), |
| 86 | columns: 5, |
| 87 | size: [SIZE; 2], |
| 88 | }, |
| 89 | ui::popup::Group { |
| 90 | heading: "", |
| 91 | cells: 1, |
| 92 | columns: 1, |
| 93 | size: [5.0 * SIZE, 28.0], |
| 94 | }, |
| 95 | ]; |
| 96 | let chosen = ui::popup::gallery(ui, id, anchor, &groups, &[], |ui, index| { |
| 97 | match symbols.get(index) { |
| 98 | Some(&symbol) => glyph(ui, symbol, None), |
| 99 | None => { |
| 100 | ui.leaf( |
| 101 | "more", |
| 102 | Spec { |
| 103 | size: [fill(), fill()], |
| 104 | text: Some("More Symbols…"), |
| 105 | icon: Some(art::SYMBOL), |
| 106 | gap: 6.0, |
| 107 | ..Spec::default() |
| 108 | }, |
| 109 | ); |
| 110 | } |
| 111 | } |
| 112 | })?; |
| 113 | Some(symbols.get(chosen).copied()) |
| 114 | } |
| 115 | |
| 116 | /// A cell's `symbol`, centred, named for assistive technology by its Unicode name. |
| 117 | fn glyph(ui: &mut Ui, symbol: char, color: Option<[f32; 4]>) { |
| 118 | let text = symbol.to_string(); |
| 119 | ui.leaf( |
| 120 | "glyph", |
| 121 | Spec { |
| 122 | size: [fill(), fill()], |
| 123 | text: Some(&text), |
| 124 | font_size: Some(18.0), |
| 125 | color, |
| 126 | center: true, |
| 127 | ..Spec::default() |
| 128 | }, |
| 129 | ); |
| 130 | let cell = ui.current(); |
| 131 | if let Some(node) = ui.access(cell) { |
| 132 | node.set_label(name(symbol)); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | /// `symbol`'s Unicode name as OneNote shows it, "EURO SIGN" as "Euro Sign"; empty where it |
| 137 | /// has none. |
| 138 | fn name(symbol: char) -> String { |
| 139 | let Some(name) = unicode_names2::name(symbol) else { |
| 140 | return String::new(); |
| 141 | }; |
| 142 | // Codes, as in "CJK UNIFIED IDEOGRAPH-4E00", stay as they are. |
| 143 | let part = |part: &str| match part.split_at_checked(1) { |
| 144 | Some((first, rest)) if !part.bytes().any(|byte| byte.is_ascii_digit()) => { |
| 145 | first.to_owned() + &rest.to_ascii_lowercase() |
| 146 | } |
| 147 | _ => part.to_owned(), |
| 148 | }; |
| 149 | let word = |word: &str| word.split('-').map(part).collect::<Vec<_>>().join("-"); |
| 150 | let name = name.to_string(); |
| 151 | name.split(' ').map(word).collect::<Vec<_>>().join(" ") |
| 152 | } |
| 153 | |
| 154 | /// A code point typed in hex, perhaps after "U+" or "0x". |
| 155 | fn code_point(text: &str) -> Option<char> { |
| 156 | let text = text.trim(); |
| 157 | let digits = ["U+", "u+", "0x", "0X"] |
| 158 | .iter() |
| 159 | .find_map(|prefix| text.strip_prefix(prefix)) |
| 160 | .unwrap_or(text); |
| 161 | if digits.is_empty() || digits.len() > 6 { |
| 162 | return None; |
| 163 | } |
| 164 | char::from_u32(u32::from_str_radix(digits, 16).ok()?) |
| 165 | } |
| 166 | |
| 167 | /// Every named character of the subsets that the interface's fonts draw, in code point order. |
| 168 | fn characters(ui: &mut Ui) -> &'static [char] { |
| 169 | static CHARACTERS: OnceLock<Vec<char>> = OnceLock::new(); |
| 170 | CHARACTERS.get_or_init(|| { |
| 171 | BLOCKS |
| 172 | .iter() |
| 173 | .flat_map(|&(start, end, _)| start..=end) |
| 174 | .filter_map(char::from_u32) |
| 175 | .filter(|symbol| { |
| 176 | unicode_names2::name(*symbol).is_some() && ui.shows(symbol.encode_utf8(&mut [0; 4])) |
| 177 | }) |
| 178 | .collect() |
| 179 | }) |
| 180 | } |
| 181 | |
| 182 | /// The characters `search` finds: the one it is, or names a code point of with "U+", then |
| 183 | /// those whose names have words starting with each of its words. |
| 184 | fn find(ui: &mut Ui, search: &str) -> Vec<char> { |
| 185 | let mut found = Vec::new(); |
| 186 | let mut letters = search.chars(); |
| 187 | if let (Some(symbol), None) = (letters.next(), letters.next()) { |
| 188 | found.push(symbol); |
| 189 | } |
| 190 | if search.starts_with(['U', 'u']) { |
| 191 | found.extend(code_point(search)); |
| 192 | } |
| 193 | let typed = found.len(); |
| 194 | let words: Vec<String> = search.split_whitespace().map(str::to_uppercase).collect(); |
| 195 | for &symbol in characters(ui) { |
| 196 | let Some(name) = unicode_names2::name(symbol) else { |
| 197 | continue; |
| 198 | }; |
| 199 | let name = name.to_string(); |
| 200 | let named = name.split([' ', '-']); |
| 201 | if words |
| 202 | .iter() |
| 203 | .all(|word| named.clone().any(|part| part.starts_with(word.as_str()))) |
| 204 | && !found[..typed].contains(&symbol) |
| 205 | { |
| 206 | found.push(symbol); |
| 207 | } |
| 208 | } |
| 209 | found |
| 210 | } |
| 211 | |
| 212 | /// The subset holding `symbol`, as an index into `BLOCKS`. |
| 213 | fn subset(symbol: char) -> Option<usize> { |
| 214 | let point = u32::from(symbol); |
| 215 | BLOCKS |
| 216 | .iter() |
| 217 | .position(|&(start, end, _)| (start..=end).contains(&point)) |
| 218 | } |
| 219 | |
| 220 | /// The grid's rows of `COLUMNS` characters. |
| 221 | struct Grid<'a>(&'a [char]); |
| 222 | |
| 223 | impl Rows for Grid<'_> { |
| 224 | fn count(&self) -> usize { |
| 225 | self.0.len().div_ceil(COLUMNS) |
| 226 | } |
| 227 | |
| 228 | fn key(&self, index: usize) -> u64 { |
| 229 | index as u64 |
| 230 | } |
| 231 | |
| 232 | fn find(&self, key: u64) -> Option<usize> { |
| 233 | (key < self.count() as u64).then_some(key as usize) |
| 234 | } |
| 235 | |
| 236 | // Cells take the clicks, not rows. |
| 237 | fn selectable(&self, _: usize) -> bool { |
| 238 | false |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | /// What the dialog's cells did this frame. |
| 243 | #[derive(Default)] |
| 244 | struct Picked { |
| 245 | symbol: Option<char>, |
| 246 | /// A double click, which inserts it. |
| 247 | inserted: bool, |
| 248 | } |
| 249 | |
| 250 | /// A grid cell showing `symbol`, filled while `selected`. |
| 251 | fn cell(ui: &mut Ui, symbol: char, selected: bool, picked: &mut Picked) { |
| 252 | let theme = ui.theme.clone(); |
| 253 | let signal = ui.open( |
| 254 | ("cell", u32::from(symbol)), |
| 255 | Spec { |
| 256 | flags: Flags::CLICKABLE, |
| 257 | axis: Axis::Y, |
| 258 | size: [px(CELL), px(CELL)], |
| 259 | fill: selected.then_some(theme.accent), |
| 260 | hover_fill: (!selected).then(|| theme.hover()), |
| 261 | border: Some(theme.chip), |
| 262 | role: Some(accesskit::Role::GridCell), |
| 263 | ..Spec::default() |
| 264 | }, |
| 265 | ); |
| 266 | let signal = ui.signal(signal); |
| 267 | if signal.pressed { |
| 268 | picked.symbol = Some(symbol); |
| 269 | picked.inserted |= signal.unit != draw::edit::SelectionUnit::Grapheme; |
| 270 | } |
| 271 | glyph(ui, symbol, selected.then_some([1.0; 4])); |
| 272 | if selected && let Some(node) = ui.access(ui.current()) { |
| 273 | node.set_selected(true); |
| 274 | } |
| 275 | ui.close(); |
| 276 | } |
| 277 | |
| 278 | impl State { |
| 279 | /// Opens More Symbols on the symbol inserted last, its search taking typing. |
| 280 | pub(crate) fn open_symbols(&mut self) { |
| 281 | let last = recent(&self.toolbar.symbols)[0]; |
| 282 | self.symbols = Some(Symbols { |
| 283 | search: String::new(), |
| 284 | found: None, |
| 285 | selected: last, |
| 286 | code: code(last), |
| 287 | inserted: false, |
| 288 | }); |
| 289 | self.ui.open_popup(id()); |
| 290 | self.ui.set_focus(Some(search_field())); |
| 291 | } |
| 292 | |
| 293 | /// Builds More Symbols while it is open: a search by name, a subset to jump to, the grid |
| 294 | /// of characters, the recently used ones, and the selection's name and code. Insert, |
| 295 | /// Enter or a double click inserts the selection and leaves the dialog open, as OneNote's. |
| 296 | pub(crate) fn symbols_dialog(&mut self) { |
| 297 | let Some(dialog) = &mut self.symbols else { |
| 298 | return; |
| 299 | }; |
| 300 | let ui = &mut self.ui; |
| 301 | if !ui.popup_open(id()) { |
| 302 | self.symbols = None; |
| 303 | return; |
| 304 | } |
| 305 | let theme = ui.theme.clone(); |
| 306 | let row = theme.font_size * 2.0; |
| 307 | let mut moves = ui::popup::navigation( |
| 308 | ui, |
| 309 | &[grid()], |
| 310 | &[ |
| 311 | NamedKey::ArrowLeft, |
| 312 | NamedKey::ArrowRight, |
| 313 | NamedKey::Home, |
| 314 | NamedKey::End, |
| 315 | ], |
| 316 | ); |
| 317 | moves.extend(ui::popup::navigation( |
| 318 | ui, |
| 319 | &[grid(), search_field()], |
| 320 | &[ |
| 321 | NamedKey::ArrowUp, |
| 322 | NamedKey::ArrowDown, |
| 323 | NamedKey::PageUp, |
| 324 | NamedKey::PageDown, |
| 325 | ], |
| 326 | )); |
| 327 | let entered = !ui::popup::navigation( |
| 328 | ui, |
| 329 | &[grid(), search_field(), code_field()], |
| 330 | &[NamedKey::Enter], |
| 331 | ) |
| 332 | .is_empty(); |
| 333 | |
| 334 | let search = dialog.search.trim().to_owned(); |
| 335 | if search.is_empty() { |
| 336 | dialog.found = None; |
| 337 | } else if dialog |
| 338 | .found |
| 339 | .as_ref() |
| 340 | .is_none_or(|(found, _)| *found != search) |
| 341 | { |
| 342 | let found = find(ui, &search); |
| 343 | if let Some(&first) = found.first() { |
| 344 | dialog.select(first); |
| 345 | } |
| 346 | dialog.found = Some((search, found)); |
| 347 | } |
| 348 | let cells = dialog |
| 349 | .found |
| 350 | .as_ref() |
| 351 | .map_or(characters(ui), |(_, found)| found.as_slice()); |
| 352 | let mut at = cells.iter().position(|symbol| *symbol == dialog.selected); |
| 353 | if let Some(last) = cells.len().checked_sub(1) { |
| 354 | for key in moves { |
| 355 | let from = at.unwrap_or(0); |
| 356 | let page = COLUMNS * ROWS; |
| 357 | let to = match key { |
| 358 | NamedKey::ArrowLeft => from.saturating_sub(1), |
| 359 | NamedKey::ArrowRight => from + 1, |
| 360 | NamedKey::ArrowUp => from.saturating_sub(COLUMNS), |
| 361 | NamedKey::ArrowDown => from + COLUMNS, |
| 362 | NamedKey::PageUp => from.saturating_sub(page), |
| 363 | NamedKey::PageDown => from + page, |
| 364 | NamedKey::Home => 0, |
| 365 | _ => last, |
| 366 | }; |
| 367 | at = Some(to.min(last)); |
| 368 | } |
| 369 | } |
| 370 | if let Some(at) = at |
| 371 | && cells[at] != dialog.selected |
| 372 | { |
| 373 | dialog.selected = cells[at]; |
| 374 | dialog.code = code(cells[at]); |
| 375 | } |
| 376 | |
| 377 | ui.open_as( |
| 378 | id(), |
| 379 | Spec { |
| 380 | axis: Axis::Y, |
| 381 | size: [px(GRID + 32.0), children()], |
| 382 | fill: Some(theme.popup), |
| 383 | border: Some(theme.chip), |
| 384 | shadow: Some(theme.shadow), |
| 385 | radius: 8.0, |
| 386 | pad: [16.0, 12.0], |
| 387 | gap: 6.0, |
| 388 | anchor: Some(Anchor::Dialog), |
| 389 | role: Some(accesskit::Role::Dialog), |
| 390 | ..Spec::default() |
| 391 | }, |
| 392 | ); |
| 393 | if let Some(node) = ui.access(id()) { |
| 394 | node.set_label(TITLE); |
| 395 | } |
| 396 | ui.leaf( |
| 397 | "title", |
| 398 | Spec { |
| 399 | size: [fill(), px(row)], |
| 400 | text: Some(TITLE), |
| 401 | bold: true, |
| 402 | role: Some(accesskit::Role::Heading), |
| 403 | ..Spec::default() |
| 404 | }, |
| 405 | ); |
| 406 | let field = Spec { |
| 407 | size: [fill(), px(row)], |
| 408 | fill: Some(theme.base), |
| 409 | border: Some(theme.accent), |
| 410 | radius: 4.0, |
| 411 | pad: [6.0, 0.0], |
| 412 | ..Spec::default() |
| 413 | }; |
| 414 | let label = |ui: &mut Ui, text: &str| { |
| 415 | ui.leaf( |
| 416 | text, |
| 417 | Spec { |
| 418 | size: [fit(), px(row)], |
| 419 | text: Some(text), |
| 420 | ..Spec::default() |
| 421 | }, |
| 422 | ); |
| 423 | }; |
| 424 | ui.open( |
| 425 | "find", |
| 426 | Spec { |
| 427 | size: [fill(), children()], |
| 428 | gap: 8.0, |
| 429 | ..Spec::default() |
| 430 | }, |
| 431 | ); |
| 432 | ui::text_field( |
| 433 | ui, |
| 434 | search_field(), |
| 435 | &mut dialog.search, |
| 436 | "Search by name", |
| 437 | field.clone(), |
| 438 | ); |
| 439 | if let Some(node) = ui.access(search_field()) { |
| 440 | node.set_label("Search by name"); |
| 441 | } |
| 442 | label(ui, "Subset:"); |
| 443 | let current = subset(dialog.selected); |
| 444 | let named = current.map_or("", |index| BLOCKS[index].2); |
| 445 | let combo = ui.id("subset"); |
| 446 | ui::shell::combo(ui, "subset", "Subset", named, 220.0, subsets(), true); |
| 447 | ui.close(); |
| 448 | |
| 449 | let mut picked = Picked::default(); |
| 450 | let selected = dialog.selected; |
| 451 | let mut row_selected = at.map(|at| (at / COLUMNS) as u64); |
| 452 | ui::list( |
| 453 | ui, |
| 454 | grid(), |
| 455 | Spec { |
| 456 | axis: Axis::Y, |
| 457 | size: [px(GRID), px(ROWS as f32 * CELL)], |
| 458 | border: Some(theme.chip), |
| 459 | role: Some(accesskit::Role::Grid), |
| 460 | ..Spec::default() |
| 461 | }, |
| 462 | ui::List { |
| 463 | rows: &Grid(cells), |
| 464 | row: CELL, |
| 465 | keys: &[], |
| 466 | hover_selects: false, |
| 467 | }, |
| 468 | &mut row_selected, |
| 469 | |ui, row| { |
| 470 | for &symbol in cells.iter().skip(row.index * COLUMNS).take(COLUMNS) { |
| 471 | cell(ui, symbol, symbol == selected, &mut picked); |
| 472 | } |
| 473 | }, |
| 474 | ); |
| 475 | if let Some(node) = ui.access(grid()) { |
| 476 | node.set_label("Characters"); |
| 477 | } |
| 478 | if cells.is_empty() { |
| 479 | ui.leaf( |
| 480 | "none", |
| 481 | Spec { |
| 482 | size: [fill(), px(row)], |
| 483 | text: Some("No characters have that name."), |
| 484 | color: Some(theme.text_dim), |
| 485 | ..Spec::default() |
| 486 | }, |
| 487 | ); |
| 488 | } |
| 489 | |
| 490 | label(ui, "Recently used symbols:"); |
| 491 | ui.open( |
| 492 | "recent", |
| 493 | Spec { |
| 494 | size: [children(), px(CELL)], |
| 495 | role: Some(accesskit::Role::Grid), |
| 496 | ..Spec::default() |
| 497 | }, |
| 498 | ); |
| 499 | for symbol in recent(&self.toolbar.symbols).into_iter().take(COLUMNS) { |
| 500 | cell(ui, symbol, symbol == selected, &mut picked); |
| 501 | } |
| 502 | ui.close(); |
| 503 | if let Some(node) = ui.access(ui.id("recent")) { |
| 504 | node.set_label("Recently used symbols"); |
| 505 | } |
| 506 | |
| 507 | ui.open( |
| 508 | "code", |
| 509 | Spec { |
| 510 | size: [fill(), children()], |
| 511 | gap: 8.0, |
| 512 | ..Spec::default() |
| 513 | }, |
| 514 | ); |
| 515 | ui.leaf( |
| 516 | "name", |
| 517 | Spec { |
| 518 | flags: Flags::CLIP, |
| 519 | size: [fill(), px(row)], |
| 520 | text: Some(&name(selected)), |
| 521 | ..Spec::default() |
| 522 | }, |
| 523 | ); |
| 524 | label(ui, "Character code:"); |
| 525 | ui::text_field( |
| 526 | ui, |
| 527 | code_field(), |
| 528 | &mut dialog.code, |
| 529 | "", |
| 530 | Spec { |
| 531 | size: [px(80.0), px(row)], |
| 532 | ..field |
| 533 | }, |
| 534 | ); |
| 535 | if let Some(node) = ui.access(code_field()) { |
| 536 | node.set_label("Character code, Unicode hex"); |
| 537 | } |
| 538 | label(ui, "Unicode (hex)"); |
| 539 | ui.close(); |
| 540 | |
| 541 | crate::buttons(ui); |
| 542 | let insert = ui::button(ui, "insert", "Insert").clicked || entered; |
| 543 | let close = if dialog.inserted { "Close" } else { "Cancel" }; |
| 544 | let closed = ui::button(ui, "cancel", close).clicked; |
| 545 | ui.close(); |
| 546 | ui.close(); |
| 547 | |
| 548 | let anchor = Anchor::Below(ui.rect(combo).unwrap_or_default()); |
| 549 | if ui.popup_open(subsets()) { |
| 550 | // Each subset that shows a character, and its first. |
| 551 | let all = characters(ui); |
| 552 | let shown: Vec<(usize, char)> = BLOCKS |
| 553 | .iter() |
| 554 | .enumerate() |
| 555 | .filter_map(|(index, &(start, end, _))| { |
| 556 | let first = all.partition_point(|symbol| u32::from(*symbol) < start); |
| 557 | let symbol = *all.get(first)?; |
| 558 | (u32::from(symbol) <= end).then_some((index, symbol)) |
| 559 | }) |
| 560 | .collect(); |
| 561 | let items: Vec<Item> = shown |
| 562 | .iter() |
| 563 | .map(|&(index, _)| Item { |
| 564 | text: BLOCKS[index].2, |
| 565 | current: Some(index) == current, |
| 566 | ..Item::default() |
| 567 | }) |
| 568 | .collect(); |
| 569 | if let Some(chosen) = ui::popup::menu(ui, subsets(), anchor, &items, Some("Subset")) { |
| 570 | dialog.search.clear(); |
| 571 | dialog.found = None; |
| 572 | dialog.select(shown[chosen].1); |
| 573 | } |
| 574 | } |
| 575 | |
| 576 | if let Some(symbol) = picked.symbol { |
| 577 | dialog.select(symbol); |
| 578 | ui.set_focus(Some(grid())); |
| 579 | } |
| 580 | if let Some(symbol) = code_point(&dialog.code) |
| 581 | && symbol != dialog.selected |
| 582 | { |
| 583 | dialog.selected = symbol; |
| 584 | } |
| 585 | if insert || picked.inserted { |
| 586 | dialog.inserted = true; |
| 587 | let symbol = dialog.selected; |
| 588 | self.commands |
| 589 | .push(Command::Choose(Choice::Symbol(Some(symbol)))); |
| 590 | } else if closed { |
| 591 | self.ui.close_popup(id()); |
| 592 | self.symbols = None; |
| 593 | } |
| 594 | } |
| 595 | } |
| 596 | |
| 597 | /// Unicode 17's blocks, but those of ideographs, Hangul syllables, surrogates, private use, |
| 598 | /// variation selectors and tags, which a symbol is not picked from. |
| 599 | const BLOCKS: &[(u32, u32, &str)] = &[ |
| 600 | (0x0000, 0x007F, "Basic Latin"), |
| 601 | (0x0080, 0x00FF, "Latin-1 Supplement"), |
| 602 | (0x0100, 0x017F, "Latin Extended-A"), |
| 603 | (0x0180, 0x024F, "Latin Extended-B"), |
| 604 | (0x0250, 0x02AF, "IPA Extensions"), |
| 605 | (0x02B0, 0x02FF, "Spacing Modifier Letters"), |
| 606 | (0x0300, 0x036F, "Combining Diacritical Marks"), |
| 607 | (0x0370, 0x03FF, "Greek and Coptic"), |
| 608 | (0x0400, 0x04FF, "Cyrillic"), |
| 609 | (0x0500, 0x052F, "Cyrillic Supplement"), |
| 610 | (0x0530, 0x058F, "Armenian"), |
| 611 | (0x0590, 0x05FF, "Hebrew"), |
| 612 | (0x0600, 0x06FF, "Arabic"), |
| 613 | (0x0700, 0x074F, "Syriac"), |
| 614 | (0x0750, 0x077F, "Arabic Supplement"), |
| 615 | (0x0780, 0x07BF, "Thaana"), |
| 616 | (0x07C0, 0x07FF, "NKo"), |
| 617 | (0x0800, 0x083F, "Samaritan"), |
| 618 | (0x0840, 0x085F, "Mandaic"), |
| 619 | (0x0860, 0x086F, "Syriac Supplement"), |
| 620 | (0x0870, 0x089F, "Arabic Extended-B"), |
| 621 | (0x08A0, 0x08FF, "Arabic Extended-A"), |
| 622 | (0x0900, 0x097F, "Devanagari"), |
| 623 | (0x0980, 0x09FF, "Bengali"), |
| 624 | (0x0A00, 0x0A7F, "Gurmukhi"), |
| 625 | (0x0A80, 0x0AFF, "Gujarati"), |
| 626 | (0x0B00, 0x0B7F, "Oriya"), |
| 627 | (0x0B80, 0x0BFF, "Tamil"), |
| 628 | (0x0C00, 0x0C7F, "Telugu"), |
| 629 | (0x0C80, 0x0CFF, "Kannada"), |
| 630 | (0x0D00, 0x0D7F, "Malayalam"), |
| 631 | (0x0D80, 0x0DFF, "Sinhala"), |
| 632 | (0x0E00, 0x0E7F, "Thai"), |
| 633 | (0x0E80, 0x0EFF, "Lao"), |
| 634 | (0x0F00, 0x0FFF, "Tibetan"), |
| 635 | (0x1000, 0x109F, "Myanmar"), |
| 636 | (0x10A0, 0x10FF, "Georgian"), |
| 637 | (0x1100, 0x11FF, "Hangul Jamo"), |
| 638 | (0x1200, 0x137F, "Ethiopic"), |
| 639 | (0x1380, 0x139F, "Ethiopic Supplement"), |
| 640 | (0x13A0, 0x13FF, "Cherokee"), |
| 641 | (0x1400, 0x167F, "Unified Canadian Aboriginal Syllabics"), |
| 642 | (0x1680, 0x169F, "Ogham"), |
| 643 | (0x16A0, 0x16FF, "Runic"), |
| 644 | (0x1700, 0x171F, "Tagalog"), |
| 645 | (0x1720, 0x173F, "Hanunoo"), |
| 646 | (0x1740, 0x175F, "Buhid"), |
| 647 | (0x1760, 0x177F, "Tagbanwa"), |
| 648 | (0x1780, 0x17FF, "Khmer"), |
| 649 | (0x1800, 0x18AF, "Mongolian"), |
| 650 | ( |
| 651 | 0x18B0, |
| 652 | 0x18FF, |
| 653 | "Unified Canadian Aboriginal Syllabics Extended", |
| 654 | ), |
| 655 | (0x1900, 0x194F, "Limbu"), |
| 656 | (0x1950, 0x197F, "Tai Le"), |
| 657 | (0x1980, 0x19DF, "New Tai Lue"), |
| 658 | (0x19E0, 0x19FF, "Khmer Symbols"), |
| 659 | (0x1A00, 0x1A1F, "Buginese"), |
| 660 | (0x1A20, 0x1AAF, "Tai Tham"), |
| 661 | (0x1AB0, 0x1AFF, "Combining Diacritical Marks Extended"), |
| 662 | (0x1B00, 0x1B7F, "Balinese"), |
| 663 | (0x1B80, 0x1BBF, "Sundanese"), |
| 664 | (0x1BC0, 0x1BFF, "Batak"), |
| 665 | (0x1C00, 0x1C4F, "Lepcha"), |
| 666 | (0x1C50, 0x1C7F, "Ol Chiki"), |
| 667 | (0x1C80, 0x1C8F, "Cyrillic Extended-C"), |
| 668 | (0x1C90, 0x1CBF, "Georgian Extended"), |
| 669 | (0x1CC0, 0x1CCF, "Sundanese Supplement"), |
| 670 | (0x1CD0, 0x1CFF, "Vedic Extensions"), |
| 671 | (0x1D00, 0x1D7F, "Phonetic Extensions"), |
| 672 | (0x1D80, 0x1DBF, "Phonetic Extensions Supplement"), |
| 673 | (0x1DC0, 0x1DFF, "Combining Diacritical Marks Supplement"), |
| 674 | (0x1E00, 0x1EFF, "Latin Extended Additional"), |
| 675 | (0x1F00, 0x1FFF, "Greek Extended"), |
| 676 | (0x2000, 0x206F, "General Punctuation"), |
| 677 | (0x2070, 0x209F, "Superscripts and Subscripts"), |
| 678 | (0x20A0, 0x20CF, "Currency Symbols"), |
| 679 | (0x20D0, 0x20FF, "Combining Diacritical Marks for Symbols"), |
| 680 | (0x2100, 0x214F, "Letterlike Symbols"), |
| 681 | (0x2150, 0x218F, "Number Forms"), |
| 682 | (0x2190, 0x21FF, "Arrows"), |
| 683 | (0x2200, 0x22FF, "Mathematical Operators"), |
| 684 | (0x2300, 0x23FF, "Miscellaneous Technical"), |
| 685 | (0x2400, 0x243F, "Control Pictures"), |
| 686 | (0x2440, 0x245F, "Optical Character Recognition"), |
| 687 | (0x2460, 0x24FF, "Enclosed Alphanumerics"), |
| 688 | (0x2500, 0x257F, "Box Drawing"), |
| 689 | (0x2580, 0x259F, "Block Elements"), |
| 690 | (0x25A0, 0x25FF, "Geometric Shapes"), |
| 691 | (0x2600, 0x26FF, "Miscellaneous Symbols"), |
| 692 | (0x2700, 0x27BF, "Dingbats"), |
| 693 | (0x27C0, 0x27EF, "Miscellaneous Mathematical Symbols-A"), |
| 694 | (0x27F0, 0x27FF, "Supplemental Arrows-A"), |
| 695 | (0x2800, 0x28FF, "Braille Patterns"), |
| 696 | (0x2900, 0x297F, "Supplemental Arrows-B"), |
| 697 | (0x2980, 0x29FF, "Miscellaneous Mathematical Symbols-B"), |
| 698 | (0x2A00, 0x2AFF, "Supplemental Mathematical Operators"), |
| 699 | (0x2B00, 0x2BFF, "Miscellaneous Symbols and Arrows"), |
| 700 | (0x2C00, 0x2C5F, "Glagolitic"), |
| 701 | (0x2C60, 0x2C7F, "Latin Extended-C"), |
| 702 | (0x2C80, 0x2CFF, "Coptic"), |
| 703 | (0x2D00, 0x2D2F, "Georgian Supplement"), |
| 704 | (0x2D30, 0x2D7F, "Tifinagh"), |
| 705 | (0x2D80, 0x2DDF, "Ethiopic Extended"), |
| 706 | (0x2DE0, 0x2DFF, "Cyrillic Extended-A"), |
| 707 | (0x2E00, 0x2E7F, "Supplemental Punctuation"), |
| 708 | (0x2E80, 0x2EFF, "CJK Radicals Supplement"), |
| 709 | (0x2F00, 0x2FDF, "Kangxi Radicals"), |
| 710 | (0x2FF0, 0x2FFF, "Ideographic Description Characters"), |
| 711 | (0x3000, 0x303F, "CJK Symbols and Punctuation"), |
| 712 | (0x3040, 0x309F, "Hiragana"), |
| 713 | (0x30A0, 0x30FF, "Katakana"), |
| 714 | (0x3100, 0x312F, "Bopomofo"), |
| 715 | (0x3130, 0x318F, "Hangul Compatibility Jamo"), |
| 716 | (0x3190, 0x319F, "Kanbun"), |
| 717 | (0x31A0, 0x31BF, "Bopomofo Extended"), |
| 718 | (0x31C0, 0x31EF, "CJK Strokes"), |
| 719 | (0x31F0, 0x31FF, "Katakana Phonetic Extensions"), |
| 720 | (0x3200, 0x32FF, "Enclosed CJK Letters and Months"), |
| 721 | (0x3300, 0x33FF, "CJK Compatibility"), |
| 722 | (0x4DC0, 0x4DFF, "Yijing Hexagram Symbols"), |
| 723 | (0xA000, 0xA48F, "Yi Syllables"), |
| 724 | (0xA490, 0xA4CF, "Yi Radicals"), |
| 725 | (0xA4D0, 0xA4FF, "Lisu"), |
| 726 | (0xA500, 0xA63F, "Vai"), |
| 727 | (0xA640, 0xA69F, "Cyrillic Extended-B"), |
| 728 | (0xA6A0, 0xA6FF, "Bamum"), |
| 729 | (0xA700, 0xA71F, "Modifier Tone Letters"), |
| 730 | (0xA720, 0xA7FF, "Latin Extended-D"), |
| 731 | (0xA800, 0xA82F, "Syloti Nagri"), |
| 732 | (0xA830, 0xA83F, "Common Indic Number Forms"), |
| 733 | (0xA840, 0xA87F, "Phags-pa"), |
| 734 | (0xA880, 0xA8DF, "Saurashtra"), |
| 735 | (0xA8E0, 0xA8FF, "Devanagari Extended"), |
| 736 | (0xA900, 0xA92F, "Kayah Li"), |
| 737 | (0xA930, 0xA95F, "Rejang"), |
| 738 | (0xA960, 0xA97F, "Hangul Jamo Extended-A"), |
| 739 | (0xA980, 0xA9DF, "Javanese"), |
| 740 | (0xA9E0, 0xA9FF, "Myanmar Extended-B"), |
| 741 | (0xAA00, 0xAA5F, "Cham"), |
| 742 | (0xAA60, 0xAA7F, "Myanmar Extended-A"), |
| 743 | (0xAA80, 0xAADF, "Tai Viet"), |
| 744 | (0xAAE0, 0xAAFF, "Meetei Mayek Extensions"), |
| 745 | (0xAB00, 0xAB2F, "Ethiopic Extended-A"), |
| 746 | (0xAB30, 0xAB6F, "Latin Extended-E"), |
| 747 | (0xAB70, 0xABBF, "Cherokee Supplement"), |
| 748 | (0xABC0, 0xABFF, "Meetei Mayek"), |
| 749 | (0xD7B0, 0xD7FF, "Hangul Jamo Extended-B"), |
| 750 | (0xFB00, 0xFB4F, "Alphabetic Presentation Forms"), |
| 751 | (0xFB50, 0xFDFF, "Arabic Presentation Forms-A"), |
| 752 | (0xFE10, 0xFE1F, "Vertical Forms"), |
| 753 | (0xFE20, 0xFE2F, "Combining Half Marks"), |
| 754 | (0xFE30, 0xFE4F, "CJK Compatibility Forms"), |
| 755 | (0xFE50, 0xFE6F, "Small Form Variants"), |
| 756 | (0xFE70, 0xFEFF, "Arabic Presentation Forms-B"), |
| 757 | (0xFF00, 0xFFEF, "Halfwidth and Fullwidth Forms"), |
| 758 | (0xFFF0, 0xFFFF, "Specials"), |
| 759 | (0x10000, 0x1007F, "Linear B Syllabary"), |
| 760 | (0x10080, 0x100FF, "Linear B Ideograms"), |
| 761 | (0x10100, 0x1013F, "Aegean Numbers"), |
| 762 | (0x10140, 0x1018F, "Ancient Greek Numbers"), |
| 763 | (0x10190, 0x101CF, "Ancient Symbols"), |
| 764 | (0x101D0, 0x101FF, "Phaistos Disc"), |
| 765 | (0x10280, 0x1029F, "Lycian"), |
| 766 | (0x102A0, 0x102DF, "Carian"), |
| 767 | (0x102E0, 0x102FF, "Coptic Epact Numbers"), |
| 768 | (0x10300, 0x1032F, "Old Italic"), |
| 769 | (0x10330, 0x1034F, "Gothic"), |
| 770 | (0x10350, 0x1037F, "Old Permic"), |
| 771 | (0x10380, 0x1039F, "Ugaritic"), |
| 772 | (0x103A0, 0x103DF, "Old Persian"), |
| 773 | (0x10400, 0x1044F, "Deseret"), |
| 774 | (0x10450, 0x1047F, "Shavian"), |
| 775 | (0x10480, 0x104AF, "Osmanya"), |
| 776 | (0x104B0, 0x104FF, "Osage"), |
| 777 | (0x10500, 0x1052F, "Elbasan"), |
| 778 | (0x10530, 0x1056F, "Caucasian Albanian"), |
| 779 | (0x10570, 0x105BF, "Vithkuqi"), |
| 780 | (0x105C0, 0x105FF, "Todhri"), |
| 781 | (0x10600, 0x1077F, "Linear A"), |
| 782 | (0x10780, 0x107BF, "Latin Extended-F"), |
| 783 | (0x10800, 0x1083F, "Cypriot Syllabary"), |
| 784 | (0x10840, 0x1085F, "Imperial Aramaic"), |
| 785 | (0x10860, 0x1087F, "Palmyrene"), |
| 786 | (0x10880, 0x108AF, "Nabataean"), |
| 787 | (0x108E0, 0x108FF, "Hatran"), |
| 788 | (0x10900, 0x1091F, "Phoenician"), |
| 789 | (0x10920, 0x1093F, "Lydian"), |
| 790 | (0x10940, 0x1095F, "Sidetic"), |
| 791 | (0x10980, 0x1099F, "Meroitic Hieroglyphs"), |
| 792 | (0x109A0, 0x109FF, "Meroitic Cursive"), |
| 793 | (0x10A00, 0x10A5F, "Kharoshthi"), |
| 794 | (0x10A60, 0x10A7F, "Old South Arabian"), |
| 795 | (0x10A80, 0x10A9F, "Old North Arabian"), |
| 796 | (0x10AC0, 0x10AFF, "Manichaean"), |
| 797 | (0x10B00, 0x10B3F, "Avestan"), |
| 798 | (0x10B40, 0x10B5F, "Inscriptional Parthian"), |
| 799 | (0x10B60, 0x10B7F, "Inscriptional Pahlavi"), |
| 800 | (0x10B80, 0x10BAF, "Psalter Pahlavi"), |
| 801 | (0x10C00, 0x10C4F, "Old Turkic"), |
| 802 | (0x10C80, 0x10CFF, "Old Hungarian"), |
| 803 | (0x10D00, 0x10D3F, "Hanifi Rohingya"), |
| 804 | (0x10D40, 0x10D8F, "Garay"), |
| 805 | (0x10E60, 0x10E7F, "Rumi Numeral Symbols"), |
| 806 | (0x10E80, 0x10EBF, "Yezidi"), |
| 807 | (0x10EC0, 0x10EFF, "Arabic Extended-C"), |
| 808 | (0x10F00, 0x10F2F, "Old Sogdian"), |
| 809 | (0x10F30, 0x10F6F, "Sogdian"), |
| 810 | (0x10F70, 0x10FAF, "Old Uyghur"), |
| 811 | (0x10FB0, 0x10FDF, "Chorasmian"), |
| 812 | (0x10FE0, 0x10FFF, "Elymaic"), |
| 813 | (0x11000, 0x1107F, "Brahmi"), |
| 814 | (0x11080, 0x110CF, "Kaithi"), |
| 815 | (0x110D0, 0x110FF, "Sora Sompeng"), |
| 816 | (0x11100, 0x1114F, "Chakma"), |
| 817 | (0x11150, 0x1117F, "Mahajani"), |
| 818 | (0x11180, 0x111DF, "Sharada"), |
| 819 | (0x111E0, 0x111FF, "Sinhala Archaic Numbers"), |
| 820 | (0x11200, 0x1124F, "Khojki"), |
| 821 | (0x11280, 0x112AF, "Multani"), |
| 822 | (0x112B0, 0x112FF, "Khudawadi"), |
| 823 | (0x11300, 0x1137F, "Grantha"), |
| 824 | (0x11380, 0x113FF, "Tulu-Tigalari"), |
| 825 | (0x11400, 0x1147F, "Newa"), |
| 826 | (0x11480, 0x114DF, "Tirhuta"), |
| 827 | (0x11580, 0x115FF, "Siddham"), |
| 828 | (0x11600, 0x1165F, "Modi"), |
| 829 | (0x11660, 0x1167F, "Mongolian Supplement"), |
| 830 | (0x11680, 0x116CF, "Takri"), |
| 831 | (0x116D0, 0x116FF, "Myanmar Extended-C"), |
| 832 | (0x11700, 0x1174F, "Ahom"), |
| 833 | (0x11800, 0x1184F, "Dogra"), |
| 834 | (0x118A0, 0x118FF, "Warang Citi"), |
| 835 | (0x11900, 0x1195F, "Dives Akuru"), |
| 836 | (0x119A0, 0x119FF, "Nandinagari"), |
| 837 | (0x11A00, 0x11A4F, "Zanabazar Square"), |
| 838 | (0x11A50, 0x11AAF, "Soyombo"), |
| 839 | ( |
| 840 | 0x11AB0, |
| 841 | 0x11ABF, |
| 842 | "Unified Canadian Aboriginal Syllabics Extended-A", |
| 843 | ), |
| 844 | (0x11AC0, 0x11AFF, "Pau Cin Hau"), |
| 845 | (0x11B00, 0x11B5F, "Devanagari Extended-A"), |
| 846 | (0x11B60, 0x11B7F, "Sharada Supplement"), |
| 847 | (0x11BC0, 0x11BFF, "Sunuwar"), |
| 848 | (0x11C00, 0x11C6F, "Bhaiksuki"), |
| 849 | (0x11C70, 0x11CBF, "Marchen"), |
| 850 | (0x11D00, 0x11D5F, "Masaram Gondi"), |
| 851 | (0x11D60, 0x11DAF, "Gunjala Gondi"), |
| 852 | (0x11DB0, 0x11DEF, "Tolong Siki"), |
| 853 | (0x11EE0, 0x11EFF, "Makasar"), |
| 854 | (0x11F00, 0x11F5F, "Kawi"), |
| 855 | (0x11FB0, 0x11FBF, "Lisu Supplement"), |
| 856 | (0x11FC0, 0x11FFF, "Tamil Supplement"), |
| 857 | (0x12000, 0x123FF, "Cuneiform"), |
| 858 | (0x12400, 0x1247F, "Cuneiform Numbers and Punctuation"), |
| 859 | (0x12480, 0x1254F, "Early Dynastic Cuneiform"), |
| 860 | (0x12F90, 0x12FFF, "Cypro-Minoan"), |
| 861 | (0x13000, 0x1342F, "Egyptian Hieroglyphs"), |
| 862 | (0x13430, 0x1345F, "Egyptian Hieroglyph Format Controls"), |
| 863 | (0x13460, 0x143FF, "Egyptian Hieroglyphs Extended-A"), |
| 864 | (0x14400, 0x1467F, "Anatolian Hieroglyphs"), |
| 865 | (0x16100, 0x1613F, "Gurung Khema"), |
| 866 | (0x16800, 0x16A3F, "Bamum Supplement"), |
| 867 | (0x16A40, 0x16A6F, "Mro"), |
| 868 | (0x16A70, 0x16ACF, "Tangsa"), |
| 869 | (0x16AD0, 0x16AFF, "Bassa Vah"), |
| 870 | (0x16B00, 0x16B8F, "Pahawh Hmong"), |
| 871 | (0x16D40, 0x16D7F, "Kirat Rai"), |
| 872 | (0x16E40, 0x16E9F, "Medefaidrin"), |
| 873 | (0x16EA0, 0x16EDF, "Beria Erfe"), |
| 874 | (0x16F00, 0x16F9F, "Miao"), |
| 875 | (0x16FE0, 0x16FFF, "Ideographic Symbols and Punctuation"), |
| 876 | (0x1AFF0, 0x1AFFF, "Kana Extended-B"), |
| 877 | (0x1B000, 0x1B0FF, "Kana Supplement"), |
| 878 | (0x1B100, 0x1B12F, "Kana Extended-A"), |
| 879 | (0x1B130, 0x1B16F, "Small Kana Extension"), |
| 880 | (0x1B170, 0x1B2FF, "Nushu"), |
| 881 | (0x1BC00, 0x1BC9F, "Duployan"), |
| 882 | (0x1BCA0, 0x1BCAF, "Shorthand Format Controls"), |
| 883 | (0x1CC00, 0x1CEBF, "Symbols for Legacy Computing Supplement"), |
| 884 | (0x1CEC0, 0x1CEFF, "Miscellaneous Symbols Supplement"), |
| 885 | (0x1CF00, 0x1CFCF, "Znamenny Musical Notation"), |
| 886 | (0x1D000, 0x1D0FF, "Byzantine Musical Symbols"), |
| 887 | (0x1D100, 0x1D1FF, "Musical Symbols"), |
| 888 | (0x1D200, 0x1D24F, "Ancient Greek Musical Notation"), |
| 889 | (0x1D2C0, 0x1D2DF, "Kaktovik Numerals"), |
| 890 | (0x1D2E0, 0x1D2FF, "Mayan Numerals"), |
| 891 | (0x1D300, 0x1D35F, "Tai Xuan Jing Symbols"), |
| 892 | (0x1D360, 0x1D37F, "Counting Rod Numerals"), |
| 893 | (0x1D400, 0x1D7FF, "Mathematical Alphanumeric Symbols"), |
| 894 | (0x1D800, 0x1DAAF, "Sutton SignWriting"), |
| 895 | (0x1DF00, 0x1DFFF, "Latin Extended-G"), |
| 896 | (0x1E000, 0x1E02F, "Glagolitic Supplement"), |
| 897 | (0x1E030, 0x1E08F, "Cyrillic Extended-D"), |
| 898 | (0x1E100, 0x1E14F, "Nyiakeng Puachue Hmong"), |
| 899 | (0x1E290, 0x1E2BF, "Toto"), |
| 900 | (0x1E2C0, 0x1E2FF, "Wancho"), |
| 901 | (0x1E4D0, 0x1E4FF, "Nag Mundari"), |
| 902 | (0x1E5D0, 0x1E5FF, "Ol Onal"), |
| 903 | (0x1E6C0, 0x1E6FF, "Tai Yo"), |
| 904 | (0x1E7E0, 0x1E7FF, "Ethiopic Extended-B"), |
| 905 | (0x1E800, 0x1E8DF, "Mende Kikakui"), |
| 906 | (0x1E900, 0x1E95F, "Adlam"), |
| 907 | (0x1EC70, 0x1ECBF, "Indic Siyaq Numbers"), |
| 908 | (0x1ED00, 0x1ED4F, "Ottoman Siyaq Numbers"), |
| 909 | (0x1EE00, 0x1EEFF, "Arabic Mathematical Alphabetic Symbols"), |
| 910 | (0x1F000, 0x1F02F, "Mahjong Tiles"), |
| 911 | (0x1F030, 0x1F09F, "Domino Tiles"), |
| 912 | (0x1F0A0, 0x1F0FF, "Playing Cards"), |
| 913 | (0x1F100, 0x1F1FF, "Enclosed Alphanumeric Supplement"), |
| 914 | (0x1F200, 0x1F2FF, "Enclosed Ideographic Supplement"), |
| 915 | (0x1F300, 0x1F5FF, "Miscellaneous Symbols and Pictographs"), |
| 916 | (0x1F600, 0x1F64F, "Emoticons"), |
| 917 | (0x1F650, 0x1F67F, "Ornamental Dingbats"), |
| 918 | (0x1F680, 0x1F6FF, "Transport and Map Symbols"), |
| 919 | (0x1F700, 0x1F77F, "Alchemical Symbols"), |
| 920 | (0x1F780, 0x1F7FF, "Geometric Shapes Extended"), |
| 921 | (0x1F800, 0x1F8FF, "Supplemental Arrows-C"), |
| 922 | (0x1F900, 0x1F9FF, "Supplemental Symbols and Pictographs"), |
| 923 | (0x1FA00, 0x1FA6F, "Chess Symbols"), |
| 924 | (0x1FA70, 0x1FAFF, "Symbols and Pictographs Extended-A"), |
| 925 | (0x1FB00, 0x1FBFF, "Symbols for Legacy Computing"), |
| 926 | ]; |
| 927 | |
| 928 | #[cfg(test)] |
| 929 | mod tests { |
| 930 | use super::*; |
| 931 | |
| 932 | #[test] |
| 933 | fn the_gallery_follows_inserts_with_onenotes_defaults() { |
| 934 | assert_eq!(recent(&[]), DEFAULTS); |
| 935 | let shown = recent(&['⅔', '±']); |
| 936 | assert_eq!(shown[..4], ['⅔', '±', '€', '£']); |
| 937 | assert_eq!(shown.len(), GALLERY); |
| 938 | assert_eq!(shown.iter().filter(|symbol| **symbol == '±').count(), 1); |
| 939 | } |
| 940 | |
| 941 | #[test] |
| 942 | fn names_and_codes_read_as_onenote_shows_them() { |
| 943 | assert_eq!(name('€'), "Euro Sign"); |
| 944 | assert_eq!(name('⅔'), "Vulgar Fraction Two Thirds"); |
| 945 | assert_eq!(name('\u{4e00}'), "Cjk Unified Ideograph-4E00"); |
| 946 | assert_eq!(name('\n'), ""); |
| 947 | assert_eq!(code('€'), "20AC"); |
| 948 | for typed in ["20ac", "U+20AC", "0x20AC"] { |
| 949 | assert_eq!(code_point(typed), Some('€')); |
| 950 | } |
| 951 | assert_eq!(code_point("D800"), None); |
| 952 | assert_eq!(code_point("1234567"), None); |
| 953 | } |
| 954 | |
| 955 | #[test] |
| 956 | fn subsets_are_ordered_and_whole_rows() { |
| 957 | for pair in BLOCKS.windows(2) { |
| 958 | assert!(pair[0].1 < pair[1].0); |
| 959 | } |
| 960 | for &(start, end, _) in BLOCKS { |
| 961 | assert_eq!((start % 16, end % 16), (0, 15)); |
| 962 | } |
| 963 | assert_eq!( |
| 964 | subset('€').map(|index| BLOCKS[index].2), |
| 965 | Some("Currency Symbols") |
| 966 | ); |
| 967 | assert_eq!(subset('\u{e000}'), None); |
| 968 | } |
| 969 | } |