mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Merge PR #620 from NikitkaRa: Animations: check effect options and accept direction words
This commit is contained in:
@@ -82,6 +82,51 @@ fn start_of(s: &str) -> AnimStart {
|
||||
}
|
||||
}
|
||||
|
||||
/// The effect option `given` as one of `allowed`. Besides the stored codes ("b", "lt", "u", …)
|
||||
/// it takes the direction words people type: "fromBottom", "bottom", "fromTopLeft",
|
||||
/// "upRight". `None` when it matches none of them: the renderer reads direction codes letter
|
||||
/// by letter, so an unchecked "fromBottom" would fly in from the right and the bottom.
|
||||
fn resolve_option(allowed: &[&str], given: &str) -> Option<String> {
|
||||
if allowed.is_empty() || allowed.contains(&given) {
|
||||
return Some(given.to_string());
|
||||
}
|
||||
let low = given.to_ascii_lowercase();
|
||||
if let Some(o) = allowed.iter().find(|o| o.to_ascii_lowercase() == low) {
|
||||
return Some(o.to_string());
|
||||
}
|
||||
let mut rest = low.strip_prefix("from").unwrap_or(&low).trim_start_matches(['-', '_', ' ']);
|
||||
// Direction words in order, each as both its edge letter and its arrow letter.
|
||||
let mut dirs: Vec<(char, char)> = Vec::new();
|
||||
while !rest.is_empty() {
|
||||
let words: [(&str, (char, char)); 8] = [
|
||||
("bottom", ('b', 'd')),
|
||||
("down", ('b', 'd')),
|
||||
("top", ('t', 'u')),
|
||||
("up", ('t', 'u')),
|
||||
("left", ('l', 'l')),
|
||||
("right", ('r', 'r')),
|
||||
("-", ('-', '-')),
|
||||
("_", ('-', '-')),
|
||||
];
|
||||
let (w, d) = words.iter().find(|(w, _)| rest.starts_with(w))?;
|
||||
if d.0 != '-' {
|
||||
dirs.push(*d);
|
||||
}
|
||||
rest = &rest[w.len()..];
|
||||
}
|
||||
if dirs.is_empty() || dirs.len() > 2 {
|
||||
return None;
|
||||
}
|
||||
let mut cands: Vec<String> = Vec::new();
|
||||
for pick in [|d: &(char, char)| d.0, |d: &(char, char)| d.1] {
|
||||
let code: String = dirs.iter().map(pick).collect();
|
||||
let rev: String = code.chars().rev().collect();
|
||||
cands.push(code);
|
||||
cands.push(rev);
|
||||
}
|
||||
cands.into_iter().find(|c| allowed.contains(&c.as_str()))
|
||||
}
|
||||
|
||||
fn build(p: &Value) -> Result<Animation> {
|
||||
let effect = str_param(p, "effect").ok_or_else(|| bad("animation", "missing `effect`"))?;
|
||||
let class = class_of(str_param(p, "class"));
|
||||
@@ -93,7 +138,12 @@ fn build(p: &Value) -> Result<Animation> {
|
||||
let mut a = Animation {
|
||||
class: info.2,
|
||||
effect: info.0.to_string(),
|
||||
option: str_param(p, "option").map(String::from).or_else(|| info.5.first().map(|o| o.to_string())).unwrap_or_default(),
|
||||
option: match str_param(p, "option") {
|
||||
Some(o) => {
|
||||
resolve_option(info.5, o).ok_or_else(|| bad("animation", format!("option `{o}` is not one of {:?} for `{}`", info.5, info.0)))?
|
||||
}
|
||||
None => info.5.first().map(|o| o.to_string()).unwrap_or_default(),
|
||||
},
|
||||
duration_ms: p.get("duration").and_then(Value::as_u64).map(|v| v.min(600_000) as u32).unwrap_or(info.4),
|
||||
delay_ms: p.get("delay").and_then(Value::as_u64).map(|v| v.min(600_000) as u32).unwrap_or(0),
|
||||
preset_id: Some(info.3),
|
||||
@@ -270,9 +320,14 @@ fn options(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
});
|
||||
each_target(s, p, |a| {
|
||||
if let Some(o) = &option {
|
||||
a.option = o.clone();
|
||||
if a.class == AnimClass::Path {
|
||||
a.path = Some(default_path(&a.effect, o));
|
||||
// Each target takes the option in its own effect's terms; one it has no such
|
||||
// option for keeps its own.
|
||||
let allowed = animation_info(&a.effect, a.class).map(|i| i.5).unwrap_or(&[]);
|
||||
if let Some(o) = resolve_option(allowed, o) {
|
||||
if a.class == AnimClass::Path {
|
||||
a.path = Some(default_path(&a.effect, &o));
|
||||
}
|
||||
a.option = o;
|
||||
}
|
||||
}
|
||||
if let Some(b) = build_mode {
|
||||
@@ -355,6 +410,26 @@ fn painter_apply(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::resolve_option;
|
||||
|
||||
#[test]
|
||||
fn direction_words_resolve_to_codes() {
|
||||
let fly = &["b", "lb", "l", "lt", "t", "rt", "r", "rb"][..];
|
||||
assert_eq!(resolve_option(fly, "fromBottom").as_deref(), Some("b"));
|
||||
assert_eq!(resolve_option(fly, "bottom").as_deref(), Some("b"));
|
||||
assert_eq!(resolve_option(fly, "fromTopLeft").as_deref(), Some("lt"));
|
||||
assert_eq!(resolve_option(fly, "fromBottomRight").as_deref(), Some("rb"));
|
||||
assert_eq!(resolve_option(fly, "lt").as_deref(), Some("lt"));
|
||||
assert_eq!(resolve_option(fly, "sideways"), None);
|
||||
assert_eq!(resolve_option(fly, "fromBottomTopLeft"), None);
|
||||
let wipe = &["r", "l", "u", "d", "ru", "lu", "rd", "ld"][..];
|
||||
assert_eq!(resolve_option(wipe, "fromBottom").as_deref(), Some("d"));
|
||||
assert_eq!(resolve_option(wipe, "upRight").as_deref(), Some("ru"));
|
||||
let split = &["horzIn", "horzOut", "vertIn", "vertOut"][..];
|
||||
assert_eq!(resolve_option(split, "HorzIn").as_deref(), Some("horzIn"));
|
||||
assert_eq!(resolve_option(&[], "anything").as_deref(), Some("anything"));
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user