Merge pull request #4957 from storytold/fix/ui-4931-4933-4944-4953

Fix #4931, #4933, #4944, #4953: save version menu, refused-save alert, Contributors header, freeform point swatch
This commit is contained in:
BFlat
2026-11-01 01:53:32 +07:00
committed by GitHub
33 changed files with 312 additions and 79 deletions
+112 -41
View File
@@ -263,22 +263,25 @@ pub fn contributors_ui(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
let counts = crate::i18n::fmt(tl!("{people} contributors · {commits} commits"), &[("people", &people), ("commits", &commits)]);
ui.label(RichText::new(counts).small().weak());
ui.separator();
egui::ScrollArea::both().max_height(SCROLL_HEIGHT).auto_shrink([false, true]).show(ui, |ui| {
if list.is_empty() {
ui.label(tl!("No contributor data was built into this copy."));
} else if v.table {
table(app, ui, &list, &mut v);
} else {
ui.horizontal_wrapped(|ui| {
for (i, c) in list.iter().enumerate() {
if i > 0 {
ui.label(RichText::new("·").weak());
if v.table && !list.is_empty() {
// The table scrolls its rows under its header itself, and sideways as a whole.
egui::ScrollArea::horizontal().auto_shrink([false, true]).show(ui, |ui| table(app, ui, &list, &mut v));
} else {
egui::ScrollArea::both().max_height(SCROLL_HEIGHT).auto_shrink([false, true]).show(ui, |ui| {
if list.is_empty() {
ui.label(tl!("No contributor data was built into this copy."));
} else {
ui.horizontal_wrapped(|ui| {
for (i, c) in list.iter().enumerate() {
if i > 0 {
ui.label(RichText::new("·").weak());
}
link(app, ui, c, v.names);
}
link(app, ui, c, v.names);
}
});
}
});
});
}
});
}
ui.data_mut(|d| d.insert_temp(id, v));
}
@@ -291,34 +294,55 @@ fn link(app: &mut VectorcraftApp, ui: &mut egui::Ui, c: &Contributor, names: Nam
r
}
/// The contributors as a table whose header row, the sort buttons, stays above the rows as they
/// scroll (#4944).
fn table(app: &mut VectorcraftApp, ui: &mut egui::Ui, list: &[&Contributor], v: &mut View) {
egui::Grid::new("credits_table").striped(true).num_columns(SortKey::ALL.len()).show(ui, |ui| {
for k in SortKey::ALL {
let arrow = if v.key == k { if v.ascending { " ▲" } else { " ▼" } } else { "" };
if ui.button(RichText::new(format!("{}{arrow}", k.label().1)).strong()).on_hover_text(k.label().0).clicked() {
if v.key == k {
v.ascending = !v.ascending;
} else {
v.key = k;
v.ascending = k.default_ascending();
}
let (row, names) = (ui.spacing().interact_size.y, v.names);
egui_extras::TableBuilder::new(ui)
.id_salt("credits_table")
.striped(true)
.columns(egui_extras::Column::auto(), SortKey::ALL.len())
.max_scroll_height(SCROLL_HEIGHT - row)
.header(row, |mut header| {
for k in SortKey::ALL {
header.col(|ui| {
let arrow = if v.key == k { if v.ascending { " ▲" } else { " ▼" } } else { "" };
if ui.button(RichText::new(format!("{}{arrow}", k.label().1)).strong()).on_hover_text(k.label().0).clicked() {
if v.key == k {
v.ascending = !v.ascending;
} else {
v.key = k;
v.ascending = k.default_ascending();
}
}
});
}
}
ui.end_row();
for c in list {
link(app, ui, c, v.names);
ui.label(group(c.prs));
ui.label(group(c.commits));
ui.label(group(c.lines_added));
ui.label(group(c.lines_deleted));
ui.label(signed(c.lines_delta()));
ui.label(group(c.binary_added));
ui.label(group(c.binary_deleted));
ui.label(day(c.first_commit));
ui.label(day(c.last_commit));
ui.end_row();
}
});
})
.body(|mut body| {
for c in list {
body.row(row, |mut r| {
r.col(|ui| {
link(app, ui, c, names);
});
let cells = [
group(c.prs),
group(c.commits),
group(c.lines_added),
group(c.lines_deleted),
signed(c.lines_delta()),
group(c.binary_added),
group(c.binary_deleted),
day(c.first_commit).to_string(),
day(c.last_commit).to_string(),
];
for text in cells {
r.col(|ui| {
ui.label(text);
});
}
});
}
});
}
/// About ▸ Models: AI models credited in Co-Authored-By trailers.
@@ -423,4 +447,51 @@ mod tests {
assert_eq!(day("2026-10-07T18:45:23Z"), "2026-10-07");
assert_eq!(day(""), "—");
}
/// #4944: in the table, the header row (the sort buttons) stays put while the rows scroll
/// under it.
#[test]
fn the_table_header_stays_above_scrolling_rows() {
let first = sorted(CONTRIBUTORS, NameMode::Username, SortKey::FirstCommit, true).first().map(|c| c.name(NameMode::Username)).unwrap();
assert!(CONTRIBUTORS.len() > 30, "enough rows to scroll");
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
ctx.data_mut(|d| d.insert_temp(egui::Id::unique("credits_view"), View { table: true, ..View::default() }));
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
let mut time = 0.0;
// Where the frame painted `text`, if it did.
let mut frame = |events: Vec<egui::Event>, find: &[&str]| {
time += 0.1;
let input = egui::RawInput { events, time: Some(time), ..Default::default() };
let mut out = ctx.run_ui(input, |ui| {
ui.set_width(660.0);
contributors_ui(&mut app, ui);
});
out.textures_delta.clear();
let at = |text: &str| {
out.shapes.iter().find_map(|c| match &c.shape {
egui::Shape::Text(t) if t.galley.text() == text => Some(t.pos),
_ => None,
})
};
find.iter().map(|t| at(t)).collect::<Vec<_>>()
};
frame(vec![], &[]);
let before = frame(vec![], &["PRs", &first]);
let (Some(header), Some(row)) = (before[0], before[1]) else { panic!("header and first row painted: {before:?}") };
let wheel = egui::Event::MouseWheel {
unit: egui::MouseWheelUnit::Point,
delta: egui::vec2(0.0, -200.0),
phase: egui::TouchPhase::Move,
modifiers: Default::default(),
source: egui::MouseWheelSource::Unknown,
};
frame(vec![egui::Event::PointerMoved(row + egui::vec2(4.0, 4.0)), wheel], &[]);
let mut after = vec![];
for _ in 0..10 {
after = frame(vec![], &["PRs", &first]);
}
assert_eq!(after[0], Some(header), "the header stays where it was");
assert!(after[1].is_none_or(|p| p.y < row.y), "the rows scrolled: {:?} from {row:?}", after[1]);
}
}
+55 -7
View File
@@ -7,12 +7,13 @@
//! editable), `path`, `format`, and one field per option (agents set them with `ui.dialog.set`).
//! Native and `.ai` files show their save options (VectorCraft Options after Save As): each
//! artboard to a separate file, with `all` (true: every artboard) or `range`, Include Linked Files,
//! Embed ICC Profiles, Create PDF-Compatible File and Use Compression. A `.ai` file's Version comes
//! first: PDF-compatible, or legacy version 7 (`aiVersion: "7"`, a file per artboard), which takes
//! only Transparent Images (`transparentImages`), Hidden Objects (`hiddenObjects`: a hidden layer,
//! omitted or made visible), Text (`outlineText`: editable or outlines) and, with several
//! artboards, a picker of the artboards to save (thumbnails to check, field `boards`, saved as
//! `range`).
//! Embed ICC Profiles, Create PDF-Compatible File and Use Compression. A native file's Version is a
//! menu of the versions VectorCraft writes (`version`, named by the releases that read each). A
//! `.ai` file's Version comes first: PDF-compatible, or legacy version 7 (`aiVersion: "7"`, a file
//! per artboard), which takes only Transparent Images (`transparentImages`), Hidden Objects
//! (`hiddenObjects`: a hidden layer, omitted or made visible), Text (`outlineText`: editable or
//! outlines) and, with several artboards, a picker of the artboards to save (thumbnails to check,
//! field `boards`, saved as `range`).
use std::sync::OnceLock;
@@ -342,6 +343,10 @@ fn option_row(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, o: &Forma
ui.end_row();
return;
}
if o.name == "version" && o.ty == "integer" {
native_version(ui, d, o);
return;
}
if o.name == "transparentImages" {
ui.label(egui::RichText::new(tl!("Transparent Images:")).color(t.text_dim)).on_hover_text(o.description);
let choices = [tl!("Flatten"), tl!("Omit")];
@@ -379,7 +384,9 @@ fn option_row(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, o: &Forma
if o.ty == "boolean" {
ui.label("");
let on = value.as_bool().unwrap_or(false);
if widgets::check_tip(ui, label.trim_end_matches(':'), on, true, o.description) {
// Versions older than compression read only plain files.
let enabled = o.name != "compress" || !uncompressed(d);
if widgets::check_tip(ui, label.trim_end_matches(':'), on && enabled, enabled, o.description) {
d.fields.insert(o.name.into(), json!(!on));
}
ui.end_row();
@@ -412,6 +419,43 @@ fn option_row(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, o: &Forma
ui.end_row();
}
/// The native versions VectorCraft writes, newest first, each with the first release that reads
/// it (the format version each release tag writes: 2 in 0.1, 3 from 0.2, 4 from 0.8).
const NATIVE_VERSIONS: [(u32, &str); 4] = [(4, "0.8"), (3, "0.2"), (2, "0.1"), (1, "0.1")];
/// A native file's Version (#4931): a menu of the versions VectorCraft writes, named by the
/// releases that read them, so a version it can't write can't be chosen. A version without
/// compression turns Use Compression off.
fn native_version(ui: &mut egui::Ui, d: &mut Dialog, o: &FormatOption) {
let t = Tokens::get(ui.ctx());
ui.label(egui::RichText::new(tl!("Version:")).color(t.text_dim)).on_hover_text(o.description);
let names: Vec<String> = NATIVE_VERSIONS
.iter()
.map(|(n, release)| crate::i18n::fmt(tl!("VectorCraft {release} and later (version {n})"), &[("release", release), ("n", &n.to_string())]))
.collect();
let names: Vec<&str> = names.iter().map(String::as_str).collect();
let at = NATIVE_VERSIONS.iter().position(|(n, _)| *n == native_version_of(d)).unwrap_or(0);
if let Some(i) = widgets::dropdown_names(ui, ("save-option", o.name), names.get(at).copied().unwrap_or_default(), &names, 254.0)
&& let Some((n, _)) = NATIVE_VERSIONS.get(i)
{
d.fields.insert(o.name.into(), json!(n));
if *n < vectorcraft_format::COMPRESSED_SINCE {
d.fields.insert("compress".into(), json!(false));
}
}
ui.end_row();
}
/// The native version the dialog will write (the current one unless chosen).
fn native_version_of(d: &Dialog) -> u32 {
d.fields.get("version").and_then(Value::as_u64).and_then(|v| u32::try_from(v).ok()).unwrap_or(vectorcraft_format::VERSION)
}
/// Is a native file to be saved in a version that can't be compressed?
fn uncompressed(d: &Dialog) -> bool {
native_version_of(d) < vectorcraft_format::COMPRESSED_SINCE
}
/// Web Save As: another format brings its own options and file extension.
fn switch_format(app: &mut VectorcraftApp, d: &mut Dialog, id: &str) {
let (Some(old), Some(new)) = (fileio::format(&d.str("format")), fileio::format(id)) else { return };
@@ -444,6 +488,10 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> DialogResult {
.iter()
.filter_map(|o| d.fields.get(o.name).filter(|v| !v.is_null() && v.as_str() != Some("")).map(|v| (o.name.to_string(), v.clone())))
.collect();
if uncompressed(d) {
// Use Compression shows off there, whatever it held before.
options.remove("compress");
}
if legacy_ai(d) {
// The artboards checked in the picker (all of them: no range).
options.remove("range");
@@ -347,3 +347,46 @@ fn eps_save_as_keeps_page_options_and_writes_eps_without_export_only_switches()
assert!(vectorcraft_eps::native(&saved[0].1).is_some());
assert_eq!(app.session.active().unwrap().format, "eps");
}
/// #4931: a native file's Version is a menu of the versions VectorCraft writes, the current one
/// first and chosen, each named by the releases that read it. An older version without
/// compression saves plain, whatever Use Compression held.
#[test]
fn native_version_is_a_menu_of_the_versions_vectorcraft_writes() {
let listed: Vec<u32> = NATIVE_VERSIONS.iter().map(|(n, _)| *n).collect();
let writable: Vec<u32> = (vectorcraft_format::MIN_VERSION..=vectorcraft_format::VERSION).rev().collect();
assert_eq!(listed, writable, "every version VectorCraft writes, newest first");
let (mut app, written, _) = desktop("art.vectorcraft");
assert_eq!(app.run("file.saveAs", json!({})).unwrap()["pending"], KIND);
let text = crate::tests_labels::painted_text(&mut app, |app, ui| super::super::show(app, ui.ctx()));
assert!(text.contains("VectorCraft 0.8 and later (version 4)"), "{text}");
assert_eq!(native_version_of(dialog(&app)), vectorcraft_format::VERSION);
let d = app.ui.dialog.as_mut().unwrap();
d.fields.insert("version".into(), json!(2));
d.fields.insert("compress".into(), json!(true));
assert!(uncompressed(d));
frame(&mut app, true);
assert!(app.ui.dialog.is_none(), "saved: {}", app.ui.status);
let (_, bytes) = written.borrow()[0].clone();
let head = String::from_utf8_lossy(&bytes[..bytes.len().min(200)]).to_string();
assert!(head.contains("\"version\": 2") || head.contains("\"version\":2"), "{head}");
}
/// #4933: a save refused before anything is written (here a version agents can still set) says
/// so in the "Can't save" alert, in the reason's words without the command's, and writes nothing.
#[test]
fn a_refused_save_says_so_in_an_alert() {
let (mut app, written, _) = desktop("art.vectorcraft");
assert_eq!(app.run("file.saveAs", json!({})).unwrap()["pending"], KIND);
app.ui.dialog.as_mut().unwrap().fields.insert("version".into(), json!(55));
frame(&mut app, false);
frame(&mut app, true);
let d = dialog(&app);
assert_eq!(
(d.kind.as_str(), d.str("message").as_str(), d.str("detail").as_str()),
(crate::dialogs::confirm::MESSAGE, "Can't save “art.vectorcraft”.", "can't save version 55: VectorCraft writes versions 1 to 4")
);
assert!(written.borrow().is_empty());
let st = app.session.active().unwrap();
assert!(st.is_dirty() && st.path.is_none(), "not saved");
}
+2 -1
View File
@@ -906,7 +906,7 @@ font-technology Variable Променливи
Edit Symbol Редактиране на символ
Edit Text Редактиране на текст
Edit the applied effect, or add another one? Да се редактира ли приведеният ефект или да се добави друг?
Edit the selected point's colour in the Color panel Редактиране на цвета на избраната точка в панела Цвят
Edit the point Редактиране на точката
Edit the stop Редактиране на опорната точка
Edit Theme Редактиране на тема
Edit theme Редактиране на тема
@@ -5926,3 +5926,4 @@ breakText Glyphs Глифи
Set Size of Each Задаване на размер на всеки
@msg select exactly two objects изберете точно два обекта
@msg give `width` or `height` задайте `width` или `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} и по-нови (версия {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1516,7 +1516,7 @@ lighting Light Svjetlo
Color: Boja:
Delete Stop Izbriši tačku
Edit Gradient Uredi gradijent
Edit the selected point's colour in the Color panel Uredite boju odabrane tačke u panelu Boja
Edit the point Uredi tačku
Edit the stop Uredi tačku
Eyedropper: click the art to sample the stop's colour Kapaljka: kliknite crtež da uzmete uzorak boje tačke
Eyedropper: click the art to sample the key color Kapaljka: kliknite crtež da uzmete uzorak ključne boje
@@ -5960,3 +5960,4 @@ shape Open Otvoren
Set Size of Each Postavi veličinu svakog
@msg select exactly two objects odaberite tačno dva objekta
@msg give `width` or `height` zadajte `width` ili `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} i novije (verzija {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1309,7 +1309,7 @@
Edit Symbol Edita el símbol
Edit Text Edita el text
Edit the applied effect, or add another one? Vols editar l’efecte aplicat o afegir-ne un altre?
Edit the selected point's colour in the Color panel Edita el color del punt seleccionat al panell Color
Edit the point Edita el punt
Edit the stop Edita la parada
Edit Theme Edita el tema
Edit theme Edita el tema
@@ -6033,3 +6033,4 @@ breakText Glyphs Glifs
Set Size of Each Defineix la mida de cadascun
@msg select exactly two objects selecciona exactament dos objectes
@msg give `width` or `height` indica `width` o `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} i posteriors (versió {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1539,7 +1539,7 @@ lighting Light Světlo
Color: Barva:
Delete Stop Odstranit zarážku
Edit Gradient Upravit přechod
Edit the selected point's colour in the Color panel Upravit barvu vybraného bodu v panelu Barva
Edit the point Upravit bod
Edit the stop Upravit zarážku
Eyedropper: click the art to sample the stop's colour Kapátko: kliknutím na kresbu naberete barvu zarážky
Eyedropper: click the art to sample the key color Kapátko: kliknutím na kresbu naberete klíčovou barvu
@@ -6042,3 +6042,4 @@ breakText Glyphs Glyfy
Set Size of Each Nastavit velikost jednotlivě
@msg select exactly two objects vyberte právě dva objekty
@msg give `width` or `height` zadejte `width` nebo `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} a novější (verze {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1453,7 +1453,7 @@ lighting Light Lys
Color: Farve:
Delete Stop Slet stop
Edit Gradient Redigér gradient
Edit the selected point's colour in the Color panel Redigér det markerede punkts farve i farvepanelet
Edit the point Redigér punktet
Edit the stop Redigér stoppet
Eyedropper: click the art to sample the stop's colour Pipette: klik på grafikken for at prøvetage stoppets farve
Eyedropper: click the art to sample the key color Pipette: klik på grafikken for at prøvetage nøglefarven
@@ -5940,3 +5940,4 @@ breakText Glyphs Glyffer
Set Size of Each Angiv størrelse for hver
@msg select exactly two objects markér præcis to objekter
@msg give `width` or `height` angiv `width` eller `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} og nyere (version {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -900,7 +900,7 @@ font-technology Variable Variabel
Edit Symbol Symbol bearbeiten
Edit Text Text bearbeiten
Edit the applied effect, or add another one? Den angewendeten Effekt bearbeiten oder einen weiteren hinzufügen?
Edit the selected point's colour in the Color panel Die Farbe des ausgewählten Punkts im Bedienfeld „Farbe“ bearbeiten
Edit the point Punkt bearbeiten
Edit the stop Farbstopp bearbeiten
Edit Theme Thema bearbeiten
Edit theme Thema bearbeiten
@@ -5628,3 +5628,4 @@ breakText Glyphs Glyphen
Set Size of Each Größe einzeln festlegen
@msg select exactly two objects wählen Sie genau zwei Objekte aus
@msg give `width` or `height` geben Sie `width` oder `height` an
VectorCraft {release} and later (version {n}) VectorCraft {release} und neuer (Version {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -876,7 +876,7 @@ font-technology Variable Μεταβλητές
Edit Symbol Επεξεργασία συμβόλου
Edit Text Επεξεργασία κειμένου
Edit the applied effect, or add another one? Επεξεργασία του εφαρμοσμένου εφέ ή προσθήκη άλλου;
Edit the selected point's colour in the Color panel Επεξεργασία του χρώματος επιλεγμένου σημείου στον πίνακα Χρώμα
Edit the point Επεξεργασία σημείου
Edit the stop Επεξεργασία σημείου ντεγκραντέ
Edit Theme Επεξεργασία θέματος
Edit theme Επεξεργασία θέματος
@@ -5995,3 +5995,4 @@ breakText Glyphs Γλύφοι
Set Size of Each Ορισμός μεγέθους καθενός
@msg select exactly two objects επιλέξτε ακριβώς δύο αντικείμενα
@msg give `width` or `height` δώστε `width` ή `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} και νεότερες (έκδοση {n})
Can't render this file because it contains an unexpected character in line 164 and column 5.
+2 -1
View File
@@ -921,7 +921,7 @@ font-technology Variable Variables
Edit Symbol Editar símbolo
Edit Text Editar texto
Edit the applied effect, or add another one? ¿Editar el efecto aplicado o agregar otro?
Edit the selected point's colour in the Color panel Edita el color del punto seleccionado en el panel Color
Edit the point Editar el punto
Edit the stop Editar la parada
Edit Theme Editar tema
Edit theme Editar tema
@@ -5985,3 +5985,4 @@ breakText Glyphs Glifos
Set Size of Each Definir el tamaño de cada uno
@msg select exactly two objects selecciona exactamente dos objetos
@msg give `width` or `height` indica `width` o `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} y posteriores (versión {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1513,7 +1513,7 @@ lighting Light Valo
Color: Väri:
Delete Stop Poista väripiste
Edit Gradient Muokkaa liukuväriä
Edit the selected point's colour in the Color panel Muokkaa valitun pisteen väriä Väri-paneelissa
Edit the point Muokkaa pistettä
Edit the stop Muokkaa väripistettä
Eyedropper: click the art to sample the stop's colour Pipetti: ota väripisteen väri näytteeksi napsauttamalla kuvitusta
Eyedropper: click the art to sample the key color Pipetti: ota avainväri näytteeksi napsauttamalla kuvitusta
@@ -6073,3 +6073,4 @@ breakText Glyphs Merkit
Set Size of Each Aseta kunkin koko
@msg select exactly two objects valitse täsmälleen kaksi objektia
@msg give `width` or `height` anna `width` tai `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} ja uudemmat (versio {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -924,7 +924,7 @@ font-technology Variable Variables
Edit Symbol Modifier le symbole
Edit Text Modifier le texte
Edit the applied effect, or add another one? Modifier l’effet appliqué ou en ajouter un autre ?
Edit the selected point's colour in the Color panel Modifier la couleur du point sélectionné dans le panneau Couleur
Edit the point Modifier le point
Edit the stop Modifier l’étape de dégradé
Edit Theme Modifier le thème
Edit theme Modifier le thème
@@ -6040,3 +6040,4 @@ breakText Glyphs Glyphes
Set Size of Each Définir la taille de chaque objet
@msg select exactly two objects sélectionnez exactement deux objets
@msg give `width` or `height` indiquez `width` ou `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} et ultérieures (version {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1189,7 +1189,7 @@ font-technology Variable Változó
Edit Symbol Szimbólum szerkesztése
Edit Text Szöveg szerkesztése
Edit the applied effect, or add another one? Szerkeszted az alkalmazott effektust, vagy újat adsz hozzá?
Edit the selected point's colour in the Color panel A kijelölt pont színe a Szín panelen szerkeszthető
Edit the point Pont szerkesztése
Edit the stop Színátmenet-pont szerkesztése
Edit Theme Téma szerkesztése
Edit theme Téma szerkesztése
@@ -6029,3 +6029,4 @@ breakText Glyphs Glifák
Set Size of Each Egyenkénti méret beállítása
@msg select exactly two objects jelölj ki pontosan két objektumot
@msg give `width` or `height` add meg a `width` vagy a `height` értéket
VectorCraft {release} and later (version {n}) VectorCraft {release} és újabb (verzió: {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1453,7 +1453,7 @@ lighting Light Cahaya
Color: Warna:
Delete Stop Hapus stop
Edit Gradient Edit gradien
Edit the selected point's colour in the Color panel Edit warna titik terpilih di panel Warna
Edit the point Edit titik
Edit the stop Edit stop
Eyedropper: click the art to sample the stop's colour Pipet: klik karya seni untuk mengambil sampel warna stop
Eyedropper: click the art to sample the key color Pipet: klik karya seni untuk mengambil sampel warna kunci
@@ -5940,3 +5940,4 @@ breakText Glyphs Glif
Set Size of Each Atur ukuran masing-masing
@msg select exactly two objects pilih tepat dua objek
@msg give `width` or `height` berikan `width` atau `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} dan yang lebih baru (versi {n})
Can't render this file because it contains an unexpected character in line 2175 and column 5.
+2 -1
View File
@@ -921,7 +921,7 @@ font-technology Variable Variabili
Edit Symbol Modifica simbolo
Edit Text Modifica testo
Edit the applied effect, or add another one? Modificare l'effetto applicato o aggiungerne un altro?
Edit the selected point's colour in the Color panel Modifica il colore del punto selezionato nel pannello Colore
Edit the point Modifica il punto
Edit the stop Modifica il cursore colore
Edit Theme Modifica tema
Edit theme Modifica tema
@@ -5662,3 +5662,4 @@ breakText Glyphs Glifi
Set Size of Each Imposta dimensione di ciascuno
@msg select exactly two objects seleziona esattamente due oggetti
@msg give `width` or `height` indica `width` o `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} e successive (versione {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1420,7 +1420,7 @@ font-technology Variable バリアブル
Edit or Apply Color Group… カラーグループを編集または適用…
Edit or Apply Colors カラーを編集/適用
Edit the applied effect, or add another one? 適用済みの効果を編集しますか? それとも別の効果を追加しますか?
Edit the selected point's colour in the Color panel 選択したポイントのカラーをカラーパネルで編集
Edit the point ポイントを編集
Edit the stop 分岐点を編集
Edit theme テーマを編集
Edit… 編集…
@@ -5728,3 +5728,4 @@ breakText Glyphs 字形
Set Size of Each 個別にサイズを設定
@msg select exactly two objects オブジェクトをちょうど 2 つ選択してください
@msg give `width` or `height` `width` または `height` を指定してください
VectorCraft {release} and later (version {n}) VectorCraft {release} 以降 (バージョン {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1370,7 +1370,7 @@ lighting Light სინათლე
Color: ფერი:
Delete Stop გადასვლის წერტილის წაშლა
Edit Gradient გრადიენტის რედაქტირება
Edit the selected point's colour in the Color panel მონიშნული წერტილის ფერის რედაქტირება ფერის პანელში
Edit the point წერტილის რედაქტირება
Edit the stop გადასვლის წერტილის რედაქტირება
Eyedropper: click the art to sample the stop's colour პიპეტი: დაწკაპეთ ნახატზე გადასვლის წერტილის ფერის ასაღებად
Eyedropper: click the art to sample the key color პიპეტი: დაწკაპეთ ნახატზე საკვანძო ფერის ასაღებად
@@ -6032,3 +6032,4 @@ breakText Glyphs გლიფები
Set Size of Each თითოეულის ზომის დაყენება
@msg select exactly two objects მონიშნეთ ზუსტად ორი ობიექტი
@msg give `width` or `height` მიუთითეთ `width` ან `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} და უფრო ახალი (ვერსია {n})
Can't render this file because it is too large.
+2 -1
View File
@@ -1411,7 +1411,7 @@ font-technology Variable 가변
Edit or Apply Color Group… 색상 그룹 편집 또는 적용…
Edit or Apply Colors 색상 편집 또는 적용
Edit the applied effect, or add another one? 적용된 효과를 편집하거나 다른 효과를 추가하시겠습니까?
Edit the selected point's colour in the Color panel 색상 패널에서 선택한 포인트의 색상 편집
Edit the point 포인트 편집
Edit the stop 정지점 편집
Edit theme 테마 편집
Edit… 편집…
@@ -5625,3 +5625,4 @@ breakText Glyphs 글리프
Set Size of Each 각각 크기 설정
@msg select exactly two objects 개체를 정확히 두 개 선택하세요
@msg give `width` or `height` `width` 또는 `height`를 지정하세요
VectorCraft {release} and later (version {n}) VectorCraft {release} 이상 (버전 {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1467,7 +1467,7 @@ lighting Light Licht
Color: Kleur:
Delete Stop Stoppunt verwijderen
Edit Gradient Verloop bewerken
Edit the selected point's colour in the Color panel Bewerk de kleur van het geselecteerde punt in het Kleur-paneel
Edit the point Punt bewerken
Edit the stop Stoppunt bewerken
Eyedropper: click the art to sample the stop's colour Pipet: klik op de illustratie om de kleur van het stoppunt over te nemen
Eyedropper: click the art to sample the key color Pipet: klik op de illustratie om de sleutelkleur over te nemen
@@ -5984,3 +5984,4 @@ breakText Glyphs Glyphs
Set Size of Each Formaat van elk instellen
@msg select exactly two objects selecteer precies twee objecten
@msg give `width` or `height` geef `width` of `height` op
VectorCraft {release} and later (version {n}) VectorCraft {release} en nieuwer (versie {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -1453,7 +1453,7 @@ lighting Light Światło
Color: Kolor:
Delete Stop Usuń punkt koloru
Edit Gradient Edytuj gradient
Edit the selected point's colour in the Color panel Edytuj kolor zaznaczonego punktu w panelu Kolor
Edit the point Edytuj punkt
Edit the stop Edytuj punkt koloru
Eyedropper: click the art to sample the stop's colour Kroplomierz: kliknij kompozycję, aby pobrać kolor punktu
Eyedropper: click the art to sample the key color Kroplomierz: kliknij kompozycję, aby pobrać kolor kluczowy
@@ -5898,3 +5898,4 @@ breakText Glyphs Glify
Set Size of Each Ustaw rozmiar każdego
@msg select exactly two objects zaznacz dokładnie dwa obiekty
@msg give `width` or `height` podaj `width` lub `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} i nowsze (wersja {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -816,7 +816,7 @@
Edit or Apply Color Group… Editar ou Aplicar Grupo de Cores…
Edit or Apply Colors Editar ou Aplicar Cores
Edit the applied effect, or add another one? Editar o efeito aplicado, ou adicionar outro?
Edit the selected point's colour in the Color panel Edite a cor do ponto selecionado no painel Cor
Edit the point Editar o ponto
Edit the stop Editar a parada
Edit theme Editar tema
Edit… Editar…
@@ -5962,3 +5962,4 @@ breakText Glyphs Glifos
Set Size of Each Definir Tamanho de Cada Um
@msg select exactly two objects selecione exatamente dois objetos
@msg give `width` or `height` informe `width` ou `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} e posteriores (versão {n})
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2 -1
View File
@@ -919,7 +919,7 @@ font-technology Variable Вариативные
Edit Symbol Редактировать символ
Edit Text Редактировать текст
Edit the applied effect, or add another one? Редактировать применённый эффект или добавить другой?
Edit the selected point's colour in the Color panel Редактируйте цвет выбранной точки на панели «Цвет»
Edit the point Редактировать точку
Edit the stop Редактировать точку перехода
Edit Theme Редактировать тему
Edit theme Редактировать тему
@@ -6020,3 +6020,4 @@ breakText Glyphs Глифы
Set Size of Each Задать размер каждого
@msg select exactly two objects выделите ровно два объекта
@msg give `width` or `height` укажите `width` или `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} и новее (версия {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -908,7 +908,7 @@ font-technology Variable Variabla
Edit Symbol Redigera symbol
Edit Text Redigera text
Edit the applied effect, or add another one? Vill du redigera den använda effekten eller lägga till en till?
Edit the selected point's colour in the Color panel Redigera den markerade punktens färg i panelen Färg
Edit the point Redigera punkten
Edit the stop Redigera stoppet
Edit Theme Redigera tema
Edit theme Redigera tema
@@ -6095,3 +6095,4 @@ breakText Glyphs Glyfer
Set Size of Each Ange storlek för vart och ett
@msg select exactly two objects markera exakt två objekt
@msg give `width` or `height` ange `width` eller `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} och senare (version {n})
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2 -1
View File
@@ -900,7 +900,7 @@ font-technology Variable Değişken
Edit Symbol Sembolü Düzenle
Edit Text Metni Düzenle
Edit the applied effect, or add another one? Uygulanan efekti düzenlemek veya başka bir efekt eklemek mi istiyorsunuz?
Edit the selected point's colour in the Color panel Seçilen noktanın rengini Renk panelinde düzenleyin
Edit the point Noktayı düzenle
Edit the stop Durağı düzenle
Edit Theme Temayı Düzenle
Edit theme Temayı düzenle
@@ -6000,3 +6000,4 @@ breakText Glyphs Glifler
Set Size of Each Her Birinin Boyutunu Ayarla
@msg select exactly two objects tam olarak iki nesne seç
@msg give `width` or `height` `width` veya `height` belirt
VectorCraft {release} and later (version {n}) VectorCraft {release} ve sonrası (sürüm {n})
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2 -1
View File
@@ -915,7 +915,7 @@ font-technology Variable Варіативні
Edit Symbol Редагувати символ
Edit Text Редагувати текст
Edit the applied effect, or add another one? Редагувати застосований ефект або додати інший?
Edit the selected point's colour in the Color panel Редагуйте колір вибраної точки на панелі «Колір»
Edit the point Редагувати точку
Edit the stop Редагувати точку переходу
Edit Theme Редагувати тему
Edit theme Редагувати тему
@@ -5642,3 +5642,4 @@ breakText Glyphs Гліфи
Set Size of Each Задати розмір кожного
@msg select exactly two objects виділіть рівно два об’єкти
@msg give `width` or `height` укажіть `width` або `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} і новіші (версія {n})
Can't render this file because it contains an unexpected character in line 178 and column 5.
+2 -1
View File
@@ -1071,7 +1071,7 @@ font-technology Variable Biến thiên
Edit Symbol Chỉnh sửa ký hiệu
Edit Text Chỉnh sửa văn bản
Edit the applied effect, or add another one? Chỉnh sửa hiệu ứng đã áp dụng, hay thêm hiệu ứng mới?
Edit the selected point's colour in the Color panel Chỉnh sửa màu của điểm đã chọn trong bảng Màu
Edit the point Chỉnh sửa điểm
Edit the stop Chỉnh sửa điểm dừng
Edit Theme Chỉnh sửa bộ màu
Edit theme Chỉnh sửa bộ màu
@@ -5644,3 +5644,4 @@ breakText Glyphs Glyph
Set Size of Each Đặt kích thước từng cái
@msg select exactly two objects hãy chọn đúng hai đối tượng
@msg give `width` or `height` hãy cho `width` hoặc `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} trở lên (phiên bản {n})
Can't render this file because it contains an unexpected character in line 200 and column 5.
+2 -1
View File
@@ -861,7 +861,7 @@
Edit Symbol 编辑符号
Edit Text 编辑文字
Edit the applied effect, or add another one? 编辑已应用的效果,还是再添加一个?
Edit the selected point's colour in the Color panel 在颜色面板中编辑所选点的颜色
Edit the point 编辑点
Edit the stop 编辑色标
Edit Theme 编辑主题
Edit theme 编辑主题
@@ -5884,3 +5884,4 @@ breakText Glyphs 字形
Set Size of Each 分别设置大小
@msg select exactly two objects 请恰好选择两个对象
@msg give `width` or `height` 请提供 `width` 或 `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} 及更高版本 (版本 {n})
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -1
View File
@@ -883,7 +883,7 @@ font-technology Variable 可變
Edit Symbol 編輯符號
Edit Text 編輯文字
Edit the applied effect, or add another one? 要編輯已套用的效果,還是新增另一個?
Edit the selected point's colour in the Color panel 在顏色面板中編輯所選點的顏色
Edit the point 編輯點
Edit the stop 編輯色標
Edit Theme 編輯主題
Edit theme 編輯主題
@@ -5681,3 +5681,4 @@ breakText Glyphs 字符
Set Size of Each 個別設定大小
@msg select exactly two objects 請選取正好兩個物件
@msg give `width` or `height` 請提供 `width` 或 `height`
VectorCraft {release} and later (version {n}) VectorCraft {release} 及更新版本 (版本 {n})
Can't render this file because it contains an unexpected character in line 6 and column 47.
+10 -1
View File
@@ -398,7 +398,16 @@ fn write_plan(app: &mut VectorcraftApp, plan: SavePlan) -> Result<Value, String>
if app.background.busy_with(uid) {
background::wait_all(app);
}
let job = fileio::save_job(&mut app.session, plan).map_err(|e| e.to_string())?;
let refused = plan.path.clone().unwrap_or_else(|| plan.name.clone());
// Refused before writing (#4933): told as a failed write is, in the reason's own words.
let job = fileio::save_job(&mut app.session, plan).map_err(|e| {
let reason = match &e {
EngineError::BadParams { msg, .. } => msg.clone(),
e => e.to_string(),
};
report_write_error(app, false, &refused, &reason);
e.to_string()
})?;
let (retargets, target) = (job.plan().retargets(), job.plan().path.clone());
let (format, warnings) = (job.plan().format.id, job.known_warnings());
let label = match &target {
+13 -3
View File
@@ -18,7 +18,7 @@
//!
//! A freeform gradient shows its section instead of the angle, aspect and slider: the Draw
//! toggle (Points or Lines: how the Gradient tool adds points) and the selected point's colour
//! (edited in the Color panel), opacity and spread, with Delete Point.
//! (click: its popover, as a stop's), opacity and spread, with Delete Point.
//!
//! The Presets menu lists ready-made gradients by theme (the built-in gradient libraries), then
//! the gradients saved with the panel menu's Save Gradient Preset…; choosing one applies it to the
@@ -484,13 +484,18 @@ fn freeform_section(app: &mut VectorcraftApp, ui: &mut Ui, g: &GradientPaint) {
let point = sel.map(|i| f.points[i]);
ui.horizontal(|ui| {
widgets::dim_label(ui, tl!("Color:"));
let (r, resp) = ui.allocate_exact_size(vec2(18.0, 18.0), Sense::hover());
let (r, resp) = ui.allocate_exact_size(vec2(18.0, 18.0), Sense::click());
let paint = point.map_or(Paint::None, |p| Paint::solid(p.color));
widgets::swatch_tile(ui, r, &paint, false, resp.hovered());
resp.on_hover_text(tl!("Edit the selected point's colour in the Color panel"));
if let Some(p) = point {
widgets::dim_label(ui, &p.color.to_hex().to_uppercase());
}
// The selected point's popover, as a stop's swatch opens the stop's (#4953).
if let Some(i) = sel
&& resp.on_hover_text(tl!("Edit the point")).clicked()
{
show_popover(app, i, r.left_bottom() + vec2(0.0, 4.0));
}
});
ui.horizontal(|ui| {
ui.add_enabled_ui(sel.is_some(), |ui| {
@@ -640,6 +645,11 @@ pub(crate) fn stop_eyedropper(app: &mut VectorcraftApp) {
/// Open the stop popover for stop `i` at screen position `at`.
fn open_popover(app: &mut VectorcraftApp, i: usize, at: Pos2) {
select_stop(app, Some(i));
show_popover(app, i, at);
}
/// The popover for the selected stop or freeform point `i`, at screen position `at`.
fn show_popover(app: &mut VectorcraftApp, i: usize, at: Pos2) {
app.ui.dialog = Some(Dialog::new("gradientStop", json!({ "index": i, "screen": [at.x, at.y], "tab": "color" })));
}
@@ -543,6 +543,31 @@ fn double_clicking_a_stop_opens_the_shared_popover_beside_it() {
assert_eq!(app.session.selected_stop(), Some(1));
}
/// #4953: with a freeform point selected, clicking the Color swatch opens the popover for that
/// point, which recolours it and leaves the other points as they were.
#[test]
fn the_freeform_color_swatch_opens_the_points_popover() {
let mut app = app(json!({"gradient": {"kind": "freeform"}}));
app.run("paint.freeform.selectPoint", json!({"index": 1})).unwrap();
let mut p = Panel::new();
let texts = p.frame(&mut app, vec![], Modifiers::NONE);
let label = text_at(&texts, "Color:").unwrap_or_else(|| panic!("the Color row: {texts:?}"));
let swatch = p.widget_after(label);
p.click(&mut app, swatch.center());
let d = app.ui.dialog.clone().expect("the popover opened");
assert_eq!(
(d.kind.as_str(), &d.fields["index"], &d.fields["screen"]),
("gradientStop", &json!(1), &json!([swatch.left(), swatch.bottom() + 4.0]))
);
let points = |app: &VectorcraftApp| shown_points(&fill(app)).points.iter().map(|p| p.color.to_hex()).collect::<Vec<_>>();
let before = points(&app);
app.ui.dialog.as_mut().unwrap().fields.insert("color".into(), json!("#ff0000"));
crate::dialogs::confirm(&mut app).unwrap();
let mut want = before.clone();
want[1] = "#ff0000".into();
assert_eq!((fill(&app).gradient.kind, points(&app)), (GradientKind::Freeform, want));
}
#[test]
fn the_stop_eyedropper_arms_the_eyedropper_for_the_stop() {
let mut app = app(json!({"gradient": {}}));
+2 -1
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (files opened from the file manager while VectorCraft runs open in its window on Windows and Linux, #4892) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (native Save As Version menu, refused-save alert, fixed Contributors table header, freeform point swatch opens its popover: #4931, #4933, #4944, #4953) · **Target:** Adobe Illustrator 2026 (30.x)
## Feature submission evidence
@@ -381,6 +381,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
| Date | Change | Summary |
|---|---|---|
| 2026-11-01 | minor | VectorCraft Options' Version is a menu of the versions VectorCraft writes, each named by the releases that read it, and an uncompressible version turns Use Compression off (#4931); a save refused before writing shows the Can't save alert with the reason (#4933); About › Contributors' table keeps its header row above the scrolling rows (#4944); the Gradient panel's freeform point swatch opens the point's popover (#4953); scores unchanged |
| 2026-10-31 | minor | A file opened from the file manager while VectorCraft runs opens as a new tab in that window, which comes to the front, on Windows and Linux (#4892); `--new-instance` opts out; scores unchanged |
| 2026-10-31 | minor | Object › Path › Smooth: Auto-Smooth takes a shaky line to one smooth curve, and direct-selected anchors smooth only their section; scores unchanged |
| 2026-10-31 | minor | New Document: More Settings has Save Preset beside the name, as Preset Details does, and saves its settings (#4893); scores and estimates unchanged |