mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-07 02:51:44 +00:00
157 lines
5.7 KiB
Rust
157 lines
5.7 KiB
Rust
//! The catalog the dialog tests and screenshots use.
|
|
|
|
use std::borrow::Cow;
|
|
use std::cell::RefCell;
|
|
use std::collections::BTreeSet;
|
|
use std::rc::Rc;
|
|
|
|
use craft_keymap::ui::{PLURALS, STRINGS, Text};
|
|
use craft_keymap::{Bindable, Binding, Key, Keymap, Kind, Platform, Scope, Scopes};
|
|
|
|
fn binding(text: &str) -> Binding {
|
|
Binding::parse(text).unwrap_or_else(|e| panic!("{text}: {e}"))
|
|
}
|
|
|
|
pub fn fixture(platform: Platform) -> Keymap {
|
|
let tool = Kind::Tool { group: Some("Marquee".into()) };
|
|
let path = |p: &[&str]| p.iter().map(|s| s.to_string()).collect::<Vec<_>>();
|
|
let timeline = Scope::new("Timeline").unwrap_or_else(Scope::app);
|
|
let catalog = vec![
|
|
Bindable::new("file.save", "Save", Scope::app()).with_path(path(&["File", "Save"])).with_default(binding("Cmd+S")),
|
|
Bindable::new("file.saveAs", "Save As…", Scope::app()).with_path(path(&["File", "Save As…"])).with_default(binding("Cmd+Shift+S")),
|
|
Bindable::new("file.export", "Export…", Scope::app()).with_path(path(&["File", "Export…"])).with_default(binding("Cmd+E")),
|
|
Bindable::new("object.front", "Bring to Front", Scope::app()).with_path(path(&["Object", "Arrange", "Bring to Front"])),
|
|
Bindable::new("tool.marquee", "Marquee Tool", Scope::app()).with_kind(tool.clone()).with_default(binding("M")),
|
|
Bindable::new("tool.ellipse", "Elliptical Marquee Tool", Scope::app()).with_kind(tool).with_default(binding("M")),
|
|
Bindable::new("timeline.loop", "Loop", timeline).with_default(binding("L")),
|
|
Bindable::new("type.bold", "Bold", Scope::app()).with_default(binding("Text: Cmd+B")),
|
|
];
|
|
Keymap::new(catalog, Scopes::new().with_panel("Timeline").with_context("Text"), platform)
|
|
}
|
|
|
|
/// `km` with a command whose label and menu path are far too long for a row.
|
|
#[allow(dead_code)]
|
|
pub fn with_long_label(km: Keymap) -> Keymap {
|
|
let long = "Distribute Selected Objects Evenly Along the Active Guide Path and Keep Their Spacing".to_string();
|
|
let mut catalog = km.catalog().to_vec();
|
|
catalog.insert(
|
|
3,
|
|
Bindable::new("object.distribute", long.as_str(), Scope::app())
|
|
.with_path(vec!["Object".into(), "Arrange and Distribute Along a Path".into(), long.clone()])
|
|
.with_default(binding("Cmd+Alt+Shift+D"))
|
|
.with_default(binding("Ctrl+Alt+Shift+F12")),
|
|
);
|
|
Keymap::new(catalog, Scopes::new().with_panel("Timeline").with_context("Text"), km.platform())
|
|
}
|
|
|
|
/// A pseudo translation: every string about 40 % longer, accented, between right-to-left marks and
|
|
/// in `⟦…⟧`, so text drawn without the marks never went through [`Text`]. Placeholders stay as
|
|
/// they are. Labels and path parts are marked too. Looked-up dialog strings missing from
|
|
/// `STRINGS` or `PLURALS` are collected in `unlisted`.
|
|
#[allow(dead_code)]
|
|
#[derive(Default)]
|
|
pub struct Pseudo {
|
|
pub unlisted: Rc<RefCell<BTreeSet<String>>>,
|
|
/// Lay the dialog out right to left.
|
|
pub rtl: bool,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl Pseudo {
|
|
pub fn mark(source: &str) -> String {
|
|
let mut out = String::from("⟦\u{200F}");
|
|
let mut in_placeholder = false;
|
|
let mut letters = 0;
|
|
for c in source.chars() {
|
|
match c {
|
|
'{' => in_placeholder = true,
|
|
'}' => in_placeholder = false,
|
|
_ => {}
|
|
}
|
|
let accented = match c {
|
|
_ if in_placeholder => c,
|
|
'a' => 'á',
|
|
'e' => 'ë',
|
|
'i' => 'ï',
|
|
'o' => 'ö',
|
|
'u' => 'ü',
|
|
'A' => 'Å',
|
|
'E' => 'É',
|
|
'O' => 'Ø',
|
|
_ => c,
|
|
};
|
|
if !in_placeholder && c.is_alphabetic() {
|
|
letters += 1;
|
|
}
|
|
out.push(accented);
|
|
}
|
|
out.extend(std::iter::repeat_n('~', letters * 2 / 5));
|
|
out.push_str("\u{200F}⟧");
|
|
out
|
|
}
|
|
}
|
|
|
|
impl Text for Pseudo {
|
|
fn ui<'a>(&'a self, source: &'a str) -> Cow<'a, str> {
|
|
if !STRINGS.contains(&source) {
|
|
self.unlisted.borrow_mut().insert(source.to_string());
|
|
}
|
|
Pseudo::mark(source).into()
|
|
}
|
|
|
|
fn plural(&self, n: u64, one: &str, other: &str) -> String {
|
|
if !PLURALS.contains(&(one, other)) {
|
|
self.unlisted.borrow_mut().insert(format!("{one}|{other}"));
|
|
}
|
|
Pseudo::mark(if n == 1 { one } else { other }).replace("{n}", &n.to_string())
|
|
}
|
|
|
|
fn command<'a>(&'a self, b: &'a Bindable) -> Cow<'a, str> {
|
|
Pseudo::mark(&b.label).into()
|
|
}
|
|
|
|
fn segment<'a>(&'a self, s: &'a str) -> Cow<'a, str> {
|
|
Pseudo::mark(s).into()
|
|
}
|
|
|
|
/// Key names spelled out in words (Escape, Space), on every platform.
|
|
fn key(&self, k: Key, _p: Platform) -> Option<String> {
|
|
let name = k.to_string();
|
|
name.chars().any(|c| c.is_ascii_lowercase()).then(|| Pseudo::mark(&name))
|
|
}
|
|
|
|
fn rtl(&self) -> bool {
|
|
self.rtl
|
|
}
|
|
}
|
|
|
|
/// English laid out right to left, to look at the mirrored layout.
|
|
#[allow(dead_code)]
|
|
pub struct EnglishRtl;
|
|
|
|
impl Text for EnglishRtl {
|
|
fn ui<'a>(&'a self, source: &'a str) -> Cow<'a, str> {
|
|
source.into()
|
|
}
|
|
|
|
fn plural(&self, n: u64, one: &str, other: &str) -> String {
|
|
(if n == 1 { one } else { other }).replace("{n}", &n.to_string())
|
|
}
|
|
|
|
fn rtl(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
/// The words stay in English order; spaces at either end change sides, as in a right-to-left
|
|
/// paragraph.
|
|
fn visual(&self, s: &str) -> String {
|
|
let core = s.trim();
|
|
if core.is_empty() {
|
|
return s.to_string();
|
|
}
|
|
let start = s.len() - s.trim_start().len();
|
|
let end = s.trim_end().len();
|
|
format!("{}{core}{}", &s[end..], &s[..start])
|
|
}
|
|
}
|