mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 21:57:04 +00:00
Merge pull request #7662 from ruchirguitar/feat/style-neon-glow
Graphic Styles: built-in Neon & Glow graphic style library
This commit is contained in:
@@ -162,6 +162,7 @@ pub const STYLE_LIBRARIES: &[BuiltinStyleLibrary] = &[
|
||||
BuiltinStyleLibrary { id: "blends-transparency", name: "Blends and Transparency", make: blends_transparency },
|
||||
BuiltinStyleLibrary { id: "game-hud", name: "Game HUD", make: game_hud },
|
||||
BuiltinStyleLibrary { id: "status-effects", name: "Status Effects", make: status_effects },
|
||||
BuiltinStyleLibrary { id: "neon-glow", name: "Neon & Glow", make: neon_glow },
|
||||
];
|
||||
|
||||
/// Built-in library `id`, made once.
|
||||
@@ -619,6 +620,44 @@ fn status_effects() -> Vec<GraphicStyle> {
|
||||
]
|
||||
}
|
||||
|
||||
/// Neon & Glow: original multi-layer procedural styles with neon tubes, laser beams, and glowing halos.
|
||||
fn neon_glow() -> Vec<GraphicStyle> {
|
||||
vec![
|
||||
style(
|
||||
"Cyan Neon",
|
||||
vec![S(StrokeLayer { opacity: 0.7, ..round("#00e5ff", 6.0) }), S(round("#ffffff", 1.5))],
|
||||
vec![fx("stylize.dropShadow", json!({"mode": "screen", "opacity": 85.0, "x": 0.0, "y": 0.0, "blur": 8.0, "color": "#00e5ff"}))],
|
||||
),
|
||||
GraphicStyle {
|
||||
blend: BlendMode::Screen,
|
||||
..style(
|
||||
"Magenta Laser",
|
||||
vec![S(StrokeLayer { blend: BlendMode::Screen, ..round("#ff007f", 5.0) }), S(round("#ffffff", 1.5))],
|
||||
vec![fx("stylize.outerGlow", json!({"mode": "screen", "opacity": 90.0, "blur": 8.0, "color": "#ff007f"}))],
|
||||
)
|
||||
},
|
||||
style(
|
||||
"Amber Tube",
|
||||
vec![S(StrokeLayer { opacity: 0.8, ..round("#ff8c00", 6.0) }), S(round("#ffea00", 1.5))],
|
||||
vec![fx("stylize.outerGlow", json!({"mode": "screen", "opacity": 85.0, "blur": 8.0, "color": "#ff8c00"}))],
|
||||
),
|
||||
style(
|
||||
"Electric Lime",
|
||||
vec![S(StrokeLayer { opacity: 0.75, ..round("#39ff14", 6.0) }), S(round("#ffffff", 1.5))],
|
||||
vec![fx("stylize.outerGlow", json!({"mode": "screen", "opacity": 90.0, "blur": 8.0, "color": "#39ff14"}))],
|
||||
),
|
||||
style(
|
||||
"Pulse Violet",
|
||||
vec![S(StrokeLayer { opacity: 0.75, ..round("#7b2cbf", 5.0) }), S(round("#e0aaff", 1.5))],
|
||||
vec![
|
||||
fx("stylize.innerGlow", json!({"mode": "screen", "opacity": 75.0, "blur": 6.0, "color": "#c77dff", "source": "edge"})),
|
||||
fx("stylize.outerGlow", json!({"mode": "screen", "opacity": 85.0, "blur": 8.0, "color": "#7b2cbf"})),
|
||||
],
|
||||
),
|
||||
style("Cobalt Phosphor", vec![S(round("#0047ab", 6.0)), S(round("#ffffff", 1.5))], vec![fx("blur.gaussian", json!({"radius": 2.0}))]),
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -750,4 +789,36 @@ mod tests {
|
||||
let Paint::Gradient(gp) = g.appearance.fill_paint() else { panic!("a gradient") };
|
||||
assert!(gp.swatch.is_none() && gp.gradient.stops.iter().all(|s| s.swatch.is_none() && s.tint == 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neon_and_glow_styles_are_defined_with_multi_strokes_and_effects() {
|
||||
let lib = builtin_style_library("neon-glow").expect("neon-glow library exists");
|
||||
assert_eq!(lib.name, "Neon & Glow");
|
||||
assert_eq!(lib.len(), 6);
|
||||
let names: Vec<&str> = lib.styles.iter().map(|g| g.name.as_str()).collect();
|
||||
assert_eq!(names, ["Cyan Neon", "Magenta Laser", "Amber Tube", "Electric Lime", "Pulse Violet", "Cobalt Phosphor"]);
|
||||
for g in &lib.styles {
|
||||
let strokes = g.appearance.items.iter().filter(|i| matches!(i, AppearanceItem::Stroke(_))).count();
|
||||
assert!(strokes >= 2, "{} has {} strokes, expected >= 2", g.name, strokes);
|
||||
assert!(!g.appearance.effects.is_empty(), "{} has effects", g.name);
|
||||
}
|
||||
let cyan = lib.style("Cyan Neon").unwrap();
|
||||
assert_eq!(cyan.appearance.effects[0].id, "stylize.dropShadow");
|
||||
let magenta = lib.style("Magenta Laser").unwrap();
|
||||
assert_eq!(magenta.blend, BlendMode::Screen);
|
||||
let pulse = lib.style("Pulse Violet").unwrap();
|
||||
let pulse_fx: Vec<&str> = pulse.appearance.effects.iter().map(|e| e.id.as_str()).collect();
|
||||
assert_eq!(pulse_fx, ["stylize.innerGlow", "stylize.outerGlow"]);
|
||||
let cobalt = lib.style("Cobalt Phosphor").unwrap();
|
||||
assert_eq!(cobalt.appearance.effects[0].id, "blur.gaussian");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neon_glow_round_trips_through_vcstyles() {
|
||||
let lib = builtin_style_library("neon-glow").unwrap();
|
||||
let text = write(&lib);
|
||||
assert!(sniff(&text));
|
||||
let read_back = read(&text, "Fallback").unwrap();
|
||||
assert_eq!(*lib, read_back);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ fn builtin_libraries_are_listed_with_original_styles_using_catalogued_effects()
|
||||
assert!(libs.iter().any(|l| l["id"] == "game-hud" && l["name"] == "Game HUD"), "{libs:?}");
|
||||
assert!(libs.iter().any(|l| l["id"] == "status-effects" && l["name"] == "Status Effects"), "{libs:?}");
|
||||
assert!(libs.iter().any(|l| l["id"] == "textures" && l["name"] == "Textures" && l["count"] == 8), "{libs:?}");
|
||||
assert!(libs.iter().any(|l| l["id"] == "neon-glow" && l["name"] == "Neon & Glow" && l["count"] == 6), "{libs:?}");
|
||||
for l in libs {
|
||||
let id = l["id"].as_str().unwrap();
|
||||
let g = run(&mut s, "graphicStyle.library", json!({"library": id}));
|
||||
@@ -69,6 +70,9 @@ fn builtin_libraries_are_listed_with_original_styles_using_catalogued_effects()
|
||||
let texture_names: Vec<&str> = textures["styles"].as_array().unwrap().iter().map(|x| x["name"].as_str().unwrap()).collect();
|
||||
let expected = ["Fine Grain", "Canvas Weave", "Burlap Sack", "Cracked Glaze", "Mosaic Floor", "Quilted Squares", "Leaded Glass", "Damp Paper"];
|
||||
assert_eq!(texture_names, expected);
|
||||
let neon = run(&mut s, "graphicStyle.library", json!({"library": "neon-glow"}));
|
||||
let neon_names: Vec<&str> = neon["styles"].as_array().unwrap().iter().map(|x| x["name"].as_str().unwrap()).collect();
|
||||
assert_eq!(neon_names, ["Cyan Neon", "Magenta Laser", "Amber Tube", "Electric Lime", "Pulse Violet", "Cobalt Phosphor"]);
|
||||
let id = rect(&mut s, 10.0, 20.0, 40.0, 30.0);
|
||||
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "game-hud", "name": "Mana Glow", "apply": true, "ids": [id.0]}));
|
||||
assert_eq!((names(&r["added"]), r["applied"].as_str()), (vec!["Mana Glow"], Some("Mana Glow")));
|
||||
@@ -419,3 +423,53 @@ fn every_builtin_style_paints_visibly_on_a_rectangle() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neon_and_glow_library_adds_and_applies_multi_stroke_styles() {
|
||||
let mut s = session();
|
||||
let list = run(&mut s, "graphicStyle.libraries", json!({}));
|
||||
let libs = list["libraries"].as_array().unwrap();
|
||||
let at = libs.iter().position(|l| l["id"] == "neon-glow").expect("neon-glow listed");
|
||||
assert_eq!(libs[at]["name"], "Neon & Glow");
|
||||
assert_eq!(libs[at]["count"], 6);
|
||||
|
||||
let (_, lib) = cmd::stylelib::library(&s, "neon-glow").unwrap();
|
||||
assert_eq!(lib.len(), 6);
|
||||
|
||||
let want = [
|
||||
("Cyan Neon", 2, "stylize.dropShadow"),
|
||||
("Magenta Laser", 2, "stylize.outerGlow"),
|
||||
("Amber Tube", 2, "stylize.outerGlow"),
|
||||
("Electric Lime", 2, "stylize.outerGlow"),
|
||||
("Pulse Violet", 2, "stylize.innerGlow"),
|
||||
("Cobalt Phosphor", 2, "blur.gaussian"),
|
||||
];
|
||||
|
||||
for (i, (name, stroke_count, first_effect)) in want.iter().enumerate() {
|
||||
let id = rect(&mut s, 10.0 + 30.0 * i as f64, 15.0, 25.0, 25.0);
|
||||
let before_undo = undo_len(&s);
|
||||
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "neon-glow", "name": name, "apply": true, "ids": [id.0]}));
|
||||
assert_eq!((names(&r["added"]), r["applied"].as_str()), (vec![*name], Some(*name)));
|
||||
assert_eq!(undo_len(&s), before_undo + 1, "{name}");
|
||||
|
||||
let node = doc(&s).node(id).unwrap();
|
||||
let strokes = node.appearance.items.iter().filter(|it| matches!(it, vectorcraft_doc::AppearanceItem::Stroke(_))).count();
|
||||
assert_eq!(strokes, *stroke_count, "{name}");
|
||||
assert_eq!(node.appearance.effects[0].id, *first_effect, "{name}");
|
||||
}
|
||||
|
||||
// Adding existing style dedupes and creates no new undo step.
|
||||
let before = (undo_len(&s), doc(&s).graphic_styles.len());
|
||||
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "neon-glow", "name": "Cyan Neon"}));
|
||||
assert_eq!((names(&r["added"]), names(&r["existing"])), (vec![], vec!["Cyan Neon"]));
|
||||
assert_eq!((undo_len(&s), doc(&s).graphic_styles.len()), before);
|
||||
|
||||
// Round-trip through serialize / open
|
||||
let b64 = run(&mut s, "document.serialize", json!({}))["dataBase64"].as_str().unwrap().to_string();
|
||||
run(&mut s, "document.open", json!({"name": "neon.vectorcraft", "dataBase64": b64}));
|
||||
for (name, _, _) in want {
|
||||
let d = doc(&s);
|
||||
let st = d.graphic_style(name).unwrap();
|
||||
assert!(st.same_look(lib.style(name).unwrap()), "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user