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:
bflatastic
2026-10-04 11:17:50 +07:00
co-authored by Claude Opus 5.5
parent d99853ed2a
commit 016fcbf34e
11 changed files with 169 additions and 45 deletions
+3
View File
@@ -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};
+11 -7
View File
@@ -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"];
+3 -3
View File
@@ -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);
}
+5 -15
View File
@@ -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.
+1 -1
View File
@@ -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)]
+69
View File
@@ -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}");
}