mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-30 04:37:29 -06:00
M3.7: neutral paint, effect and stroke labels
The wide-gamut RGB working space is now "Wide Gamut RGB (1998 primaries)"; its old name still resolves everywhere (cms::profile / canonical_name, Color Settings, Assign Profile, documents and settings saved earlier). The Effect menu section is "Vector Effects", stroke width profiles are Lens / Taper Start / Taper End, dash, arrowhead-tip and gradient-on-stroke tooltips are reworded, the Window library submenus hold neutral disabled placeholders until the generated libraries land, and the Swatches menu offers "Save Swatch Library…" (disabled). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d99853ed2a
commit
016fcbf34e
@@ -31,6 +31,9 @@ pub mod unsaved;
|
||||
pub mod widgets;
|
||||
pub mod workspaces;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_labels;
|
||||
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ pub enum Item {
|
||||
Todo(&'static str, &'static str),
|
||||
Sub(&'static str, Vec<Item>),
|
||||
Sep,
|
||||
/// Section header (disabled label, e.g. "Illustrator Effects").
|
||||
/// Section header (disabled label, e.g. "Vector Effects").
|
||||
Header(&'static str),
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ fn todos(label: &'static str, sc: &'static str) -> Item {
|
||||
fn sub(label: &'static str, items: Vec<Item>) -> Item {
|
||||
Item::Sub(label, items)
|
||||
}
|
||||
/// Window → … Libraries until the code-generated libraries land (disabled entries).
|
||||
fn library_placeholders() -> Vec<Item> {
|
||||
vec![todo("Built-in Libraries"), todo("User Defined"), Sep, todo("Other Library…")]
|
||||
}
|
||||
use Item::Sep;
|
||||
|
||||
/// UI-level commands: (id, label, shortcut, params doc).
|
||||
@@ -1218,10 +1222,10 @@ pub fn menu_tree() -> Vec<(&'static str, Vec<Item>)> {
|
||||
),
|
||||
todo("Variables"),
|
||||
Sep,
|
||||
sub("Brush Libraries", vec![todo("Arrows"), todo("Artistic"), todo("Borders"), todo("Decorative")]),
|
||||
sub("Graphic Style Libraries", vec![todo("Additive"), todo("Artistic Effects"), todo("Buttons and Rollovers")]),
|
||||
sub("Swatch Libraries", vec![todo("Art History"), todo("Celebration"), todo("Color Properties"), todo("Nature"), todo("Web")]),
|
||||
sub("Symbol Libraries", vec![todo("Arrows"), todo("Charts"), todo("Web Buttons and Bars")]),
|
||||
sub("Brush Libraries", library_placeholders()),
|
||||
sub("Graphic Style Libraries", library_placeholders()),
|
||||
sub("Swatch Libraries", library_placeholders()),
|
||||
sub("Symbol Libraries", library_placeholders()),
|
||||
],
|
||||
),
|
||||
(
|
||||
@@ -1576,7 +1580,7 @@ fn font_items() -> Vec<Item> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The Effect menu, built from the effects catalogue (Illustrator Effects), plus raster effects.
|
||||
/// The Effect menu, built from the effects catalogue (vector effects), plus raster effects.
|
||||
fn effect_menu() -> Vec<Item> {
|
||||
let cat = vectorcraft_effects::effect_catalog();
|
||||
let mut out = vec![
|
||||
@@ -1585,7 +1589,7 @@ fn effect_menu() -> Vec<Item> {
|
||||
Sep,
|
||||
c("Document Raster Effects Settings…", "document.rasterEffectsSettings"),
|
||||
Sep,
|
||||
Item::Header("Illustrator Effects"),
|
||||
Item::Header("Vector Effects"),
|
||||
];
|
||||
// Submenus in Illustrator's order.
|
||||
let order = ["3D and Materials", "Convert to Shape", "Distort & Transform", "Path", "Pathfinder", "Stylize", "SVG Filters", "Warp", "Blur"];
|
||||
|
||||
@@ -157,9 +157,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
widgets::dim_label(ui, "Stroke:");
|
||||
for (icon, tip) in [
|
||||
("dc-stroke-center", "Apply gradient within stroke"),
|
||||
("dc-stroke-outside", "Apply gradient along stroke (on the roadmap)"),
|
||||
("dc-stroke-inside", "Apply gradient across stroke (on the roadmap)"),
|
||||
("dc-stroke-center", "Gradient inside stroke"),
|
||||
("dc-stroke-outside", "Gradient along stroke (on the roadmap)"),
|
||||
("dc-stroke-inside", "Gradient across stroke (on the roadmap)"),
|
||||
] {
|
||||
widgets::icon_button_enabled(ui, icon, tip, icon == "dc-stroke-center" && !app.session.fill_active, false, 22.0);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ pub const WEIGHT_PRESETS: [f64; 22] =
|
||||
[0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0];
|
||||
|
||||
/// Width profile presets: (id for `stroke.set`, label).
|
||||
pub const PROFILES: [(&str, &str); 4] =
|
||||
[("uniform", "Uniform"), ("lens", "Width Profile 1"), ("taperStart", "Width Profile 4"), ("taperEnd", "Width Profile 5")];
|
||||
pub const PROFILES: [(&str, &str); 4] = [("uniform", "Uniform"), ("lens", "Lens"), ("taperStart", "Taper Start"), ("taperEnd", "Taper End")];
|
||||
|
||||
/// Which preset a stroke's profile is (None profile = uniform).
|
||||
pub fn profile_id(p: Option<&WidthProfile>) -> &'static str {
|
||||
@@ -165,19 +164,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
ui.add_space((ui.available_width() - 56.0).max(0.0));
|
||||
let on = dash.is_some();
|
||||
if widgets::icon_button_enabled(ui, "dc-dash-exact", "Preserves exact dash and gap lengths", on && !align_corners, on, 24.0).clicked() {
|
||||
if widgets::icon_button_enabled(ui, "dc-dash-exact", "Exact dash lengths", on && !align_corners, on, 24.0).clicked() {
|
||||
set(app, json!({"dash": dash_pattern(&fields), "alignDashes": false}));
|
||||
}
|
||||
if widgets::icon_button_enabled(
|
||||
ui,
|
||||
"dc-dash-align",
|
||||
"Aligns dashes to corners and path ends, adjusting lengths to fit",
|
||||
on && align_corners,
|
||||
on,
|
||||
24.0,
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
if widgets::icon_button_enabled(ui, "dc-dash-align", "Fit dashes to corners and ends", on && align_corners, on, 24.0).clicked() {
|
||||
set(app, json!({"dash": dash_pattern(&fields), "alignDashes": true}));
|
||||
}
|
||||
});
|
||||
@@ -242,8 +232,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
row_label(ui, "Align:");
|
||||
widgets::icon_button_enabled(ui, "dc-cap-square", "Extend arrow tip beyond end of path (on the roadmap)", true, false, 22.0);
|
||||
widgets::icon_button_enabled(ui, "dc-cap-butt", "Place arrow tip at end of path (on the roadmap)", false, false, 22.0);
|
||||
widgets::icon_button_enabled(ui, "dc-cap-square", "Tip extends past the end (on the roadmap)", true, false, 22.0);
|
||||
widgets::icon_button_enabled(ui, "dc-cap-butt", "Tip on the end point (on the roadmap)", false, false, 22.0);
|
||||
});
|
||||
widgets::divider(ui);
|
||||
// Profile.
|
||||
|
||||
@@ -431,7 +431,7 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
menu_item(ui, "Spot Colors…", false, false);
|
||||
ui.separator();
|
||||
menu_item(ui, "Open Swatch Library", false, false);
|
||||
menu_item(ui, "Export Swatches Library as ASE…", false, false);
|
||||
menu_item(ui, "Save Swatch Library…", false, false);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
//! Neutral wording in menus and panels: what users read names VectorCraft's own features.
|
||||
|
||||
use egui::epaint::Shape;
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
use crate::VectorcraftApp;
|
||||
use crate::menus::{self, Item};
|
||||
|
||||
fn app() -> VectorcraftApp {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 100, "height": 100})).unwrap();
|
||||
app
|
||||
}
|
||||
|
||||
/// Every string painted by `f` in one headless frame, one per line.
|
||||
fn painted_text(app: &mut VectorcraftApp, mut f: impl FnMut(&mut VectorcraftApp, &mut egui::Ui)) -> String {
|
||||
fn collect(s: &Shape, out: &mut String) {
|
||||
match s {
|
||||
Shape::Text(t) => {
|
||||
out.push_str(t.galley.text());
|
||||
out.push('\n');
|
||||
}
|
||||
Shape::Vec(v) => v.iter().for_each(|s| collect(s, out)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let ctx = egui::Context::default();
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| f(app, ui));
|
||||
out.textures_delta.clear();
|
||||
let mut text = String::new();
|
||||
for c in &out.shapes {
|
||||
collect(&c.shape, &mut text);
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effect_menu_has_a_vector_effects_section() {
|
||||
let tree = menus::menu_tree();
|
||||
let (_, effect) = tree.iter().find(|(t, _)| *t == "Effect").expect("Effect menu");
|
||||
assert!(effect.iter().any(|i| matches!(i, Item::Header("Vector Effects"))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn library_submenus_are_disabled_placeholders() {
|
||||
let app = app();
|
||||
let entries = menus::menu_entries(&app);
|
||||
for lib in ["Brush Libraries", "Graphic Style Libraries", "Swatch Libraries", "Symbol Libraries"] {
|
||||
let items: Vec<_> = entries.iter().filter(|e| e.path == ["Window", lib]).collect();
|
||||
let labels: Vec<&str> = items.iter().map(|e| e.label.as_str()).collect();
|
||||
assert_eq!(labels, ["Built-in Libraries", "User Defined", "Other Library…"], "{lib}");
|
||||
assert!(items.iter().all(|e| !e.enabled && e.command.is_none()), "{lib}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stroke_profiles_have_descriptive_names() {
|
||||
let labels: Vec<&str> = crate::panels::stroke::PROFILES.iter().map(|p| p.1).collect();
|
||||
assert_eq!(labels, ["Uniform", "Lens", "Taper Start", "Taper End"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swatches_menu_offers_save_swatch_library() {
|
||||
let mut app = app();
|
||||
let text = painted_text(&mut app, crate::panels::swatches::menu);
|
||||
assert!(text.contains("Save Swatch Library…"), "{text}");
|
||||
assert!(!text.contains("ASE"), "{text}");
|
||||
}
|
||||
Reference in New Issue
Block a user