Part of #868: legacy .ai options dialog, Transparent Images and an artboard picker

The .ai options dialog offers Legacy (version 7). In that mode it shows Transparent Images
(Flatten, Omit) and, with more than one artboard, thumbnails to check with the file each is
saved to (long names keep their end). OK is greyed out until an artboard is checked. A save
panel's Enter no longer confirms the options dialog it opens.
This commit is contained in:
Dan Green
2026-10-16 01:36:24 +07:00
committed by bflatastic
parent bcae7ca486
commit 91ed4853a8
20 changed files with 486 additions and 13 deletions
@@ -596,7 +596,7 @@ fn format_settings(ui: &mut egui::Ui, d: &mut Dialog, id: &str) {
}
/// A small cached rendering of artboard `i` (keyed by document revision).
fn artboard_thumb(app: &mut VectorcraftApp, ctx: &egui::Context, i: usize) -> Option<egui::TextureHandle> {
pub(super) fn artboard_thumb(app: &mut VectorcraftApp, ctx: &egui::Context, i: usize) -> Option<egui::TextureHandle> {
let st = app.session.active()?;
let key = egui::Id::new(("ab-thumb", i, st.revision));
if let Some(t) = ctx.data(|d| d.get_temp::<egui::TextureHandle>(key)) {
+16 -2
View File
@@ -149,6 +149,8 @@ pub(crate) struct DialogSpec {
/// The OK button's label when it depends on the app (Export for Screens' "Download" on the
/// web); `ok` when none. Only with an `ok`.
pub ok_label: Option<fn(&VectorcraftApp) -> &'static str>,
/// Can OK (or Enter) apply the dialog as it is? None: always. The body says why not.
pub ok_enabled: Option<fn(&VectorcraftApp, &Dialog) -> bool>,
}
impl DialogSpec {
@@ -170,6 +172,7 @@ impl DialogSpec {
max_width: None,
preview: false,
ok_label: None,
ok_enabled: None,
};
/// A dialog that draws its own window and confirms through its module.
@@ -380,6 +383,10 @@ pub(crate) fn settle(app: &mut VectorcraftApp) {
missing_fonts::open_next(app);
}
/// The field of a dialog that a save panel opened (its format's options): Enter doesn't confirm it
/// until Enter has been let go.
pub(crate) const AFTER_PANEL: &str = "__afterPanel";
pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
about::show(app, ctx);
settle(app);
@@ -418,8 +425,14 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
return window(app, ctx);
}
let mut ok = false;
let mut ok_enabled = true;
let mut cancel = false;
let mut discard = false;
// Opened as a save panel closed: the Enter that closed it isn't this dialog's, until it is let go.
let after_panel = d.bool(AFTER_PANEL);
if after_panel && !ctx.input(|i| i.key_down(egui::Key::Enter) || i.key_pressed(egui::Key::Enter)) {
d.fields.remove(AFTER_PANEL);
}
let heading = (spec.heading)(&d);
let revolves = revolve_gizmo::active(&d);
if revolves {
@@ -450,9 +463,10 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
// right-to-left layout would otherwise take all the room left in the window, so the
// window could never shrink to its content.
let row = egui::vec2(ui.min_rect().width(), ui.spacing().interact_size.y);
ok_enabled = spec.ok_enabled.is_none_or(|f| f(app, &d));
ui.allocate_ui_with_layout(row, egui::Layout::right_to_left(egui::Align::Center), |ui| {
if let Some(label) = spec.ok.map(|ok| spec.ok_label.map_or(ok, |f| f(app)))
&& widgets::primary_button(ui, label).clicked()
&& widgets::primary_button_enabled(ui, label, ok_enabled).clicked()
{
ok = true;
}
@@ -466,7 +480,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
}
});
});
if spec.ok.is_some() && ctx.input(|i| i.key_pressed(egui::Key::Enter)) {
if spec.ok.is_some() && ok_enabled && !after_panel && ctx.input(|i| i.key_pressed(egui::Key::Enter)) {
ok = true;
}
if discard {
+312 -5
View File
@@ -7,7 +7,10 @@
//! 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.
//! 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 and, with several artboards, a picker of the artboards to save
//! (thumbnails to check, field `boards`, saved as `range`).
use std::sync::OnceLock;
@@ -22,7 +25,12 @@ use crate::{VectorcraftApp, io, widgets};
/// Dialog kind.
pub const KIND: &str = "saveOptions";
pub(super) const SPEC: DialogSpec = DialogSpec { heading, body, confirm, min_width: 380.0, ..DialogSpec::FORM };
pub(super) const SPEC: DialogSpec = DialogSpec { heading, body, confirm, min_width: 380.0, ok_enabled: Some(can_save), ..DialogSpec::FORM };
/// OK saves a legacy `.ai` file only with an artboard checked.
fn can_save(app: &VectorcraftApp, d: &Dialog) -> bool {
!legacy_ai(d) || picked_boards(d, app.session.active().map_or(0, |st| st.doc.artboards.len())).iter().any(|b| *b)
}
fn heading(d: &Dialog) -> String {
if d.bool("__pick") {
@@ -33,6 +41,9 @@ fn heading(d: &Dialog) -> String {
}
.into();
}
if legacy_ai(d) {
return tl!("Legacy .ai Options").into();
}
crate::i18n::fmt(tl!("{format} Options"), &[("format", fileio::format(&d.str("format")).map_or("", |f| f.label))])
}
@@ -133,21 +144,197 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
ui.end_row();
}
}
for o in shown(f) {
// Version first; the legacy version takes only its own options.
let legacy = legacy_ai(d);
let mut rows: Vec<&FormatOption> = shown(f).filter(|o| LEGACY_ONLY.contains(&o.name) == legacy || o.name == "aiVersion").collect();
rows.sort_by_key(|o| o.name != "aiVersion");
for o in rows {
option_row(app, ui, d, o);
}
});
if legacy_ai(d) && app.session.active().is_some_and(|st| st.doc.artboards.len() > 1) {
artboard_picker(app, ui, d);
}
if let Some(id) = switch.filter(|id| *id != f.id) {
switch_format(app, d, id);
}
false
}
/// One option: a checkbox, an artboard menu, a number or a text field.
/// The `.ai` options only the legacy version takes (and the only ones it takes, besides the
/// version).
const LEGACY_ONLY: &[&str] = &["transparentImages"];
/// Is a `.ai` file to be saved in the legacy version 7 (`aiVersion: "7"`)?
fn legacy_ai(d: &Dialog) -> bool {
d.str("format") == "ai" && d.str("aiVersion").trim() == "7"
}
/// The artboards a legacy `.ai` save writes (field `boards`: one bool per artboard), as last
/// saved (`range`), else all of them.
fn picked_boards(d: &Dialog, n: usize) -> Vec<bool> {
if let Some(a) = d.fields.get("boards").and_then(Value::as_array) {
let mut b: Vec<bool> = a.iter().map(|b| b.as_bool().unwrap_or(false)).collect();
b.resize(n, false);
return b;
}
let range = d.str("range");
match (ArtboardPick { range: Some(range.clone()), ..Default::default() }).resolve(n) {
Ok(Some(chosen)) if !range.trim().is_empty() => (0..n).map(|i| chosen.contains(&i)).collect(),
_ => vec![true; n],
}
}
/// The file each checked artboard is saved to (`None` for the others): the file itself when one
/// is checked, else `{stem}-{artboard}.ai` beside it, as the save names them.
fn board_files(app: &VectorcraftApp, d: &Dialog, boards: &[bool]) -> Vec<Option<String>> {
let checked: Vec<usize> = boards.iter().enumerate().filter(|(_, on)| **on).map(|(i, _)| i).collect();
let path = d.str("path");
let file = std::path::Path::new(path.trim());
let name = file.file_name().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
let stem = file.file_stem().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
let mut names = match (app.session.active(), checked.len()) {
(_, 1) => vec![name],
(Some(st), _) => fileio::artboard_file_names(&st.doc, &checked).into_iter().map(|b| format!("{stem}-{b}.ai")).collect(),
(None, _) => vec![],
}
.into_iter();
boards.iter().map(|on| if *on { names.next() } else { None }).collect()
}
/// `text` as it fits in `width` with `font`: whole, else its start cut short before its last
/// `tail` characters ("desktop-i…-Artboard-1.ai"), else as much of its end as fits.
fn elide_before_tail(ui: &egui::Ui, text: &str, tail: usize, font: &egui::FontId, width: f32) -> String {
let fits = |s: &str| ui.painter().layout_no_wrap(s.to_string(), font.clone(), egui::Color32::WHITE).size().x <= width;
if fits(text) {
return text.to_string();
}
let chars: Vec<char> = text.chars().collect();
let split = chars.len().saturating_sub(tail);
let (head, end) = chars.split_at(split);
let end: String = end.iter().collect();
for p in (1..head.len()).rev() {
let s = format!("{}…{end}", head.iter().take(p).collect::<String>());
if fits(&s) {
return s;
}
}
for k in (1..=chars.len()).rev() {
let s = format!("…{}", chars.iter().skip(chars.len() - k).collect::<String>());
if fits(&s) {
return s;
}
}
"…".into()
}
/// Legacy `.ai`: the artboards to save, as thumbnails to check; each is saved as a file of its own.
fn artboard_picker(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
const TILE: egui::Vec2 = egui::vec2(116.0, 84.0);
let t = Tokens::get(ui.ctx());
let names: Vec<String> = app.session.active().map(|st| st.doc.artboards.iter().map(|a| a.name.clone()).collect()).unwrap_or_default();
let n = names.len();
let mut boards = picked_boards(d, n);
let before = boards.clone();
ui.add_space(10.0);
ui.add(
egui::Label::new(
egui::RichText::new(tl!("A legacy .ai file holds one artboard: each artboard you check is saved as a separate file.")).color(t.text_dim),
)
.wrap(),
);
ui.add_space(4.0);
ui.horizontal(|ui| {
if ui.small_button(tl!("Select All")).clicked() {
boards.iter_mut().for_each(|b| *b = true);
}
if ui.small_button(tl!("Clear")).clicked() {
boards.iter_mut().for_each(|b| *b = false);
}
let checked = boards.iter().filter(|b| **b).count();
let (count, total) = (checked.to_string(), n.to_string());
let text = crate::i18n::fmt(tl!("{count} of {total} artboards"), &[("count", &count), ("total", &total)]);
ui.label(egui::RichText::new(text).color(if checked == 0 { t.warning } else { t.text_dim }));
if checked == 0 {
ui.label(egui::RichText::new(tl!("Check an artboard to save.")).color(t.warning));
}
});
ui.add_space(4.0);
let files = board_files(app, d, &boards);
let stem = std::path::Path::new(d.str("path").trim()).file_stem().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
egui::ScrollArea::vertical().max_height(330.0).show(ui, |ui| {
ui.with_layout(egui::Layout::left_to_right(egui::Align::Min).with_main_wrap(true), |ui| {
ui.spacing_mut().item_spacing = egui::vec2(10.0, 10.0);
for (i, name) in names.iter().enumerate() {
let on = boards.get(i).copied().unwrap_or(false);
ui.allocate_ui(egui::vec2(TILE.x, TILE.y + 44.0), |ui| {
ui.vertical(|ui| {
ui.set_width(TILE.x);
let (r, resp) = ui.allocate_exact_size(TILE, egui::Sense::click());
ui.painter().rect_filled(r, 3.0, t.pasteboard);
let inner = r.shrink(6.0);
match super::export_for_screens::artboard_thumb(app, ui.ctx(), i) {
Some(tex) => {
let sz = tex.size_vec2();
let s = (inner.width() / sz.x).min(inner.height() / sz.y).min(1.0);
let uv = egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0));
ui.painter().image(tex.id(), egui::Rect::from_center_size(inner.center(), sz * s), uv, egui::Color32::WHITE);
}
None => {
ui.painter().rect_filled(inner, 0.0, egui::Color32::WHITE);
}
}
let stroke = if on { egui::Stroke::new(2.0, t.accent) } else { egui::Stroke::new(1.0, t.border) };
ui.painter().rect_stroke(r, 3.0, stroke, egui::StrokeKind::Inside);
let mut flip = resp.on_hover_text(name.as_str()).clicked();
flip |= widgets::check_name(ui, name, on);
let file = files.get(i).cloned().flatten().unwrap_or_default();
// The end tells the files apart: the start gives way first.
let font = egui::TextStyle::Small.resolve(ui.style());
let tail = file.strip_prefix(stem.as_str()).map_or(0, |t| t.chars().count());
let shown = elide_before_tail(ui, &file, tail, &font, TILE.x);
let label = ui.add(egui::Label::new(egui::RichText::new(&shown).small().color(t.text_dim)));
if shown != file {
label.on_hover_text(file);
}
if flip && let Some(b) = boards.get_mut(i) {
*b ^= true;
}
});
});
}
});
});
if boards != before || !d.fields.contains_key("boards") {
d.fields.insert("boards".into(), json!(boards));
}
}
/// One option: a checkbox, an artboard menu, a version menu, a number or a text field.
fn option_row(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, o: &FormatOption) {
let t = Tokens::get(ui.ctx());
let label = label(o);
let value = d.fields.get(o.name).cloned().unwrap_or(Value::Null);
if o.name == "aiVersion" {
ui.label(egui::RichText::new(tl!("Version:")).color(t.text_dim)).on_hover_text(o.description);
let versions = [tl!("PDF-compatible"), tl!("Legacy (version 7)")];
let at = usize::from(legacy_ai(d));
if let Some(i) = widgets::dropdown(ui, ("save-option", o.name), versions.get(at).copied().unwrap_or_default(), &versions, 254.0) {
d.fields.insert(o.name.into(), json!(if i == 1 { "7" } else { "pdf" }));
}
ui.end_row();
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")];
let at = usize::from(d.str("transparentImages").trim().eq_ignore_ascii_case("omit"));
if let Some(i) = widgets::dropdown(ui, ("save-option", o.name), choices.get(at).copied().unwrap_or_default(), &choices, 254.0) {
d.fields.insert(o.name.into(), json!(if i == 1 { "omit" } else { "flatten" }));
}
ui.end_row();
return;
}
if o.ty == "boolean" {
ui.label("");
let on = value.as_bool().unwrap_or(false);
@@ -216,7 +403,18 @@ 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 separates(f) {
if legacy_ai(d) {
// The artboards checked in the picker (all of them: no range).
options.remove("range");
let n = app.session.active().map_or(0, |s| s.doc.artboards.len());
let boards = picked_boards(d, n);
if !boards.iter().any(|b| *b) {
return Err(tl!("Check an artboard to save.").into());
}
if !boards.iter().all(|b| *b) {
options.insert("range".into(), json!(super::export_for_screens::range_text(&boards)));
}
} else if separates(f) {
if !d.bool("separateArtboards") || all_artboards(d) {
options.remove("range");
} else {
@@ -335,6 +533,115 @@ mod tests {
assert!(written.borrow()[1].1.starts_with(b"<?xml"));
}
#[test]
fn save_as_ai_version_7_writes_a_file_per_artboard() {
let (mut app, written, _) = desktop("art.ai");
app.run("artboard.new", json!({"x": 200, "y": 0, "width": 100, "height": 80, "name": "Back"})).unwrap();
assert_eq!(app.run("file.saveAs", json!({})).unwrap()["pending"], KIND);
assert_eq!(dialog(&app).str("aiVersion"), "pdf", "PDF-compatible unless asked");
assert_eq!(dialog(&app).str("transparentImages"), "flatten");
frame(&mut app, false);
app.ui.dialog.as_mut().unwrap().fields.insert("aiVersion".into(), json!("7"));
app.ui.dialog.as_mut().unwrap().fields.insert("transparentImages".into(), json!("omit"));
frame(&mut app, false);
frame(&mut app, true);
assert!(app.ui.dialog.is_none());
let names: Vec<String> = written.borrow().iter().map(|(p, _)| p.clone()).collect();
assert_eq!(names, ["art-Artboard-1.ai", "art-Back.ai"]);
assert!(written.borrow().iter().all(|(_, b)| b.starts_with(b"%!PS")));
let st = app.session.active().unwrap();
assert_eq!((st.format, st.save_options["aiVersion"].clone()), ("ai", json!("7")), "Save remembers the version");
assert_eq!(st.save_options["transparentImages"], json!("omit"));
}
/// An Illustrator file opened, then saved as `.ai`: the options dialog stays open through the
/// Enter that closed the save panel, and only an Enter after it is let go confirms it.
#[test]
fn legacy_ai_saves_the_artboards_checked_in_its_picker() {
let (mut app, written, _) = desktop("art.ai");
for (i, name) in ["Back", "Side", "Top"].iter().enumerate() {
let x = 200.0 * (i + 1) as f64;
app.run("artboard.new", json!({"x": x, "y": 0, "width": 100, "height": 80 + 20 * i, "name": name})).unwrap();
app.run("shape.ellipse", json!({"x": x + 10.0, "y": 10, "width": 40, "height": 40})).unwrap();
}
app.run("file.saveAs", json!({})).unwrap();
app.ui.dialog.as_mut().unwrap().fields.insert("aiVersion".into(), json!("7"));
frame(&mut app, false);
assert_eq!(dialog(&app).fields["boards"], json!([true, true, true, true]), "every artboard to start with");
if let Some(dir) = std::env::var_os("CRAFT_UI_SAVE_OPTIONS_SHOT") {
let mut ready = false;
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(900.0, 760.0)).wgpu().build_ui_state(
move |ui, app: &mut VectorcraftApp| {
if !ready {
theme::install_fonts(ui.ctx());
theme::apply(ui.ctx(), theme::Brightness::MediumDark);
ready = true;
return;
}
super::super::show(app, ui.ctx());
},
app,
);
h.run_steps(6);
h.render().unwrap().save(std::path::Path::new(&dir).join("legacy-ai-picker.png")).unwrap();
let fields = &mut h.state_mut().ui.dialog.as_mut().unwrap().fields;
fields.insert("path".into(), json!("desktop-ideas1-vc5.ai"));
fields.insert("boards".into(), json!([false, false, false, false]));
h.run_steps(2);
h.state_mut().ui.dialog.as_mut().unwrap().fields.insert("boards".into(), json!([true, true, false, true]));
h.run_steps(2);
h.render().unwrap().save(std::path::Path::new(&dir).join("legacy-ai-picker-long.png")).unwrap();
h.state_mut().ui.dialog.as_mut().unwrap().fields.insert("boards".into(), json!([false, false, false, false]));
h.run_steps(2);
h.render().unwrap().save(std::path::Path::new(&dir).join("legacy-ai-picker-none.png")).unwrap();
app = h.into_state();
app.ui.dialog.as_mut().unwrap().fields.insert("path".into(), json!("art.ai"));
}
// Clearing them all keeps the dialog open.
app.ui.dialog.as_mut().unwrap().fields.insert("boards".into(), json!([false, false, false, false]));
let d = dialog(&app).clone();
assert!(!can_save(&app, &d), "OK is greyed out");
frame(&mut app, true);
assert!(app.ui.dialog.is_some(), "Enter does nothing");
assert!(confirm(&mut app, &d).is_err());
assert!(written.borrow().is_empty());
app.ui.dialog.as_mut().unwrap().fields.insert("boards".into(), json!([true, false, true, false]));
frame(&mut app, true);
assert!(app.ui.dialog.is_none());
let names: Vec<String> = written.borrow().iter().map(|(p, _)| p.clone()).collect();
assert_eq!(names, ["art-Artboard-1.ai", "art-Side.ai"]);
assert_eq!(board_files_of(&app, [true, false, true, false]), [Some("art-Artboard-1.ai".into()), None, Some("art-Side.ai".into()), None]);
assert_eq!(board_files_of(&app, [false, true, false, false]), [None, Some("art.ai".into()), None, None], "one: the file itself");
// Save As again starts from the artboards saved.
assert_eq!(app.session.active().unwrap().save_options["range"], json!("1, 3"));
app.run("file.saveAs", json!({})).unwrap();
frame(&mut app, false);
assert_eq!(dialog(&app).fields["boards"], json!([true, false, true, false]));
}
fn board_files_of(app: &VectorcraftApp, boards: [bool; 4]) -> Vec<Option<String>> {
let d = Dialog { kind: KIND.into(), fields: [("path".to_string(), json!("art.ai"))].into_iter().collect() };
board_files(app, &d, &boards)
}
#[test]
fn the_enter_that_closed_the_save_panel_does_not_confirm_the_options() {
let (mut app, written, _) = desktop("again.ai");
let dir = vectorcraft_testkit::temp_dir("ai-options-enter");
let orig = dir.join("orig.ai");
std::fs::write(&orig, vectorcraft_testkit::ai::ai(&vectorcraft_testkit::ai::sample_data(), vectorcraft_testkit::ai::page_pdf())).unwrap();
app.run("document.open", json!({"path": orig.to_string_lossy()})).unwrap();
assert_eq!(app.run("file.saveAs", json!({})).unwrap()["pending"], KIND);
frame(&mut app, true);
assert!(app.ui.dialog.is_some(), "the panel's Enter doesn't confirm it");
assert!(written.borrow().is_empty());
// Let go, then pressed again: OK.
frame(&mut app, false);
frame(&mut app, true);
assert!(app.ui.dialog.is_none());
assert_eq!(written.borrow()[0].0, "again.ai");
}
#[test]
fn save_a_copy_and_template_suggest_their_names() {
let (mut app, written, picks) = desktop("copy.vectorcraft");
+9
View File
@@ -3178,6 +3178,15 @@ theme Appearance Motiv
Large Velké
Large Tabs Velké karty
Legacy vector clipboard Starší vektorová schránka
Legacy (version 7) Starší (verze 7)
Transparent Images: Průhledné obrazy:
Flatten Sloučit
Omit Vynechat
Legacy .ai Options Starší soubor .ai – volby
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. Soubor .ai ve starší verzi obsahuje jedinou kreslicí plochu: každá zaškrtnutá kreslicí plocha se uloží jako samostatný soubor.
{count} of {total} artboards {count} z {total} kreslicích ploch
Check an artboard to save. Zaškrtněte kreslicí plochu, kterou chcete uložit.
PDF-compatible Kompatibilní s PDF
Legacy vector clipboard (no transparency) Starší vektorová schránka (bez průhlednosti)
Line Color Barva čar
Lines Čáry
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -1461,6 +1461,15 @@
Left: Links:
Legacy Macintosh RGB (Gamma 1.8) Älteres Macintosh-RGB (Gamma 1,8)
Legacy vector clipboard Ältere Vektorzwischenablage
Legacy (version 7) Älter (Version 7)
Transparent Images: Transparente Bilder:
Flatten Reduzieren
Omit Weglassen
Legacy .ai Options Optionen für ältere .ai-Datei
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. Eine ältere .ai-Datei enthält eine Zeichenfläche: Jede aktivierte Zeichenfläche wird als eigene Datei gespeichert.
{count} of {total} artboards {count} von {total} Zeichenflächen
Check an artboard to save. Aktivieren Sie eine Zeichenfläche zum Speichern.
PDF-compatible PDF-kompatibel
Legacy vector clipboard (no transparency) Ältere Vektorzwischenablage (ohne Transparenz)
Less Weniger
Letter Letter
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -1477,6 +1477,15 @@
Left: Izquierda:
Legacy Macintosh RGB (Gamma 1.8) RGB de Macintosh heredado (Gamma 1.8)
Legacy vector clipboard Portapapeles vectorial heredado
Legacy (version 7) Heredado (versión 7)
Transparent Images: Imágenes transparentes:
Flatten Acoplar
Omit Omitir
Legacy .ai Options Opciones de .ai heredado
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. Un archivo .ai heredado contiene una mesa de trabajo: cada mesa de trabajo marcada se guarda como un archivo independiente.
{count} of {total} artboards {count} de {total} mesas de trabajo
Check an artboard to save. Marque una mesa de trabajo para guardarla.
PDF-compatible Compatible con PDF
Legacy vector clipboard (no transparency) Portapapeles vectorial heredado (sin transparencia)
Less Menos
Letter Carta
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -1476,6 +1476,15 @@
Left: Gauche :
Legacy Macintosh RGB (Gamma 1.8) RVB Macintosh hérité (gamma 1,8)
Legacy vector clipboard Presse-papiers vectoriel hérité
Legacy (version 7) Hérité (version 7)
Transparent Images: Images transparentes :
Flatten Aplatir
Omit Omettre
Legacy .ai Options Options .ai hérité
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. Un fichier .ai hérité contient un seul plan de travail : chaque plan de travail coché est enregistré dans un fichier distinct.
{count} of {total} artboards {count} plans de travail sur {total}
Check an artboard to save. Cochez un plan de travail à enregistrer.
PDF-compatible Compatible PDF
Legacy vector clipboard (no transparency) Presse-papiers vectoriel hérité (sans transparence)
Less Moins
Letter Lettre US
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -1880,6 +1880,15 @@
Left: Bal:
Legacy Macintosh RGB (Gamma 1.8) Régi Macintosh RGB (gamma 1,8)
Legacy vector clipboard Régi vektoros vágólap
Legacy (version 7) Régi (7-as verzió)
Transparent Images: Átlátszó képek:
Flatten Egyesítés
Omit Kihagyás
Legacy .ai Options Régi .ai beállításai
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. A régi .ai fájl egy rajztáblát tartalmaz: minden bejelölt rajztábla külön fájlba kerül.
{count} of {total} artboards {count} / {total} rajztábla
Check an artboard to save. Jelöljön be egy mentendő rajztáblát.
PDF-compatible PDF-kompatibilis
Legacy vector clipboard (no transparency) Régi vektoros vágólap (átlátszóság nélkül)
Legal Legal
Length: Hossz:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -1479,6 +1479,15 @@
Left: Sinistra:
Legacy Macintosh RGB (Gamma 1.8) RGB Macintosh tradizionale (gamma 1.8)
Legacy vector clipboard Appunti vettoriali tradizionali
Legacy (version 7) Tradizionale (versione 7)
Transparent Images: Immagini trasparenti:
Flatten Unisci
Omit Ometti
Legacy .ai Options Opzioni .ai tradizionale
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. Un file .ai tradizionale contiene una tavola da disegno: ogni tavola selezionata viene salvata come file separato.
{count} of {total} artboards {count} di {total} tavole da disegno
Check an artboard to save. Seleziona una tavola da disegno da salvare.
PDF-compatible Compatibile PDF
Legacy vector clipboard (no transparency) Appunti vettoriali tradizionali (senza trasparenza)
Less Meno
Letter Lettera
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -1666,6 +1666,15 @@
Left Plane 左面
Left: 左:
Legacy vector clipboard 従来のベクタークリップボード
Legacy (version 7) 従来 (バージョン 7)
Transparent Images: 透明な画像:
Flatten 統合
Omit 省略
Legacy .ai Options 従来の .ai オプション
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. 従来の .ai ファイルにはアートボードが 1 つしか入りません。チェックした各アートボードは別々のファイルとして保存されます。
{count} of {total} artboards {total} 個中 {count} 個のアートボード
Check an artboard to save. 保存するアートボードをチェックしてください。
PDF-compatible PDF 互換
Legacy vector clipboard (no transparency) 従来のベクタークリップボード (透明なし)
Less 少なく
Ligatures are off while the text is tracked. テキストにトラッキングが設定されている間、合字は無効です。
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -1661,6 +1661,15 @@
Left Plane 왼쪽 평면
Left: 왼쪽:
Legacy vector clipboard 레거시 벡터 클립보드
Legacy (version 7) 레거시 (버전 7)
Transparent Images: 투명 이미지:
Flatten 병합
Omit 생략
Legacy .ai Options 레거시 .ai 옵션
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. 레거시 .ai 파일에는 대지가 하나만 들어갑니다. 선택한 각 대지는 별도의 파일로 저장됩니다.
{count} of {total} artboards 대지 {total}개 중 {count}개
Check an artboard to save. 저장할 대지를 선택하십시오.
PDF-compatible PDF 호환
Legacy vector clipboard (no transparency) 레거시 벡터 클립보드 (투명도 없음)
Less 간단히
Ligatures are off while the text is tracked. 자간이 적용된 텍스트에서는 합자가 꺼집니다.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -4651,6 +4651,15 @@ mark-style Japanese Japońskie
Softness Miękkość
Ambient Light Światło otoczenia
Light behind the object Światło za obiektem
Legacy (version 7) Starsza (wersja 7)
Transparent Images: Przezroczyste obrazy:
Flatten Spłaszcz
Omit Pomiń
Legacy .ai Options Opcje starszego pliku .ai
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. Starszy plik .ai zawiera jeden obszar kompozycji: każdy zaznaczony obszar kompozycji jest zapisywany jako osobny plik.
{count} of {total} artboards {count} z {total} obszarów kompozycji
Check an artboard to save. Zaznacz obszar kompozycji, aby zapisać.
PDF-compatible Zgodny z PDF
# Dimension tool and Object › Dimensions (dimension groups, their dialog and messages).
Dimension Wymiar
Drawing : real size — 1:10 labels 1 unit on the page as 10. Rysunek : rozmiar rzeczywisty — 1:10 opisuje 1 jednostkę na stronie jako 10.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -1225,6 +1225,15 @@
Left: Esquerda:
Legacy Macintosh RGB (Gamma 1.8) Macintosh RGB Legado (Gama 1.8)
Legacy vector clipboard Área de transferência vetorial legada
Legacy (version 7) Legado (versão 7)
Transparent Images: Imagens transparentes:
Flatten Achatar
Omit Omitir
Legacy .ai Options Opções de .ai legado
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. Um arquivo .ai legado contém uma prancheta: cada prancheta marcada é salva como um arquivo separado.
{count} of {total} artboards {count} de {total} pranchetas
Check an artboard to save. Marque uma prancheta para salvar.
PDF-compatible Compatível com PDF
Legacy vector clipboard (no transparency) Área de transferência vetorial legada (sem transparência)
Less Menos
Levels… Níveis…
Can't render this file because it contains an unexpected character in line 6 and column 47.
+9
View File
@@ -1477,6 +1477,15 @@
Left: Слева:
Legacy Macintosh RGB (Gamma 1.8) Устаревший Macintosh RGB (гамма 1.8)
Legacy vector clipboard Устаревший векторный буфер обмена
Legacy (version 7) Устаревший (версия 7)
Transparent Images: Прозрачные изображения:
Flatten Объединить
Omit Пропустить
Legacy .ai Options Параметры устаревшего .ai
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. Устаревший файл .ai содержит одну монтажную область: каждая отмеченная монтажная область сохраняется в отдельный файл.
{count} of {total} artboards {count} из {total} монтажных областей
Check an artboard to save. Отметьте монтажную область для сохранения.
PDF-compatible Совместимый с PDF
Legacy vector clipboard (no transparency) Устаревший векторный буфер обмена (без прозрачности)
Less Меньше
Letter Letter
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -1473,6 +1473,15 @@
Left: Зліва:
Legacy Macintosh RGB (Gamma 1.8) Застарілий Macintosh RGB (гама 1.8)
Legacy vector clipboard Застарілий векторний буфер обміну
Legacy (version 7) Застарілий (версія 7)
Transparent Images: Прозорі зображення:
Flatten Об’єднати
Omit Пропустити
Legacy .ai Options Параметри застарілого .ai
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. Застарілий файл .ai містить одну монтажну область: кожна позначена монтажна область зберігається в окремий файл.
{count} of {total} artboards {count} з {total} монтажних областей
Check an artboard to save. Позначте монтажну область для збереження.
PDF-compatible Сумісний із PDF
Legacy vector clipboard (no transparency) Застарілий векторний буфер обміну (без прозорості)
Less Менше
Letter Letter
Can't render this file because it contains an unexpected character in line 713 and column 40.
+9
View File
@@ -1694,6 +1694,15 @@
Left-Justified Tab Tab căn trái
Left-to-Right Paragraph Direction Đoạn văn từ trái sang phải
Left: Trái:
Legacy (version 7) Cũ (phiên bản 7)
Transparent Images: Ảnh trong suốt:
Flatten Làm phẳng
Omit Bỏ qua
Legacy .ai Options Tùy chọn .ai cũ
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. Một tệp .ai cũ chỉ chứa một bảng vẽ: mỗi bảng vẽ bạn chọn được lưu thành một tệp riêng.
{count} of {total} artboards {count} trên {total} bảng vẽ
Check an artboard to save. Hãy chọn một bảng vẽ để lưu.
PDF-compatible Tương thích PDF
Legacy Macintosh RGB (Gamma 1.8) RGB Macintosh cũ (Gamma 1.8)
Legacy vector clipboard Bảng tạm vector cũ
Legacy vector clipboard (no transparency) Bảng tạm vector cũ (không có trong suốt)
1 # Vietnamese (vi) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
+9
View File
@@ -1381,6 +1381,15 @@
Left: 左:
Legacy Macintosh RGB (Gamma 1.8) 旧版 Macintosh RGB (Gamma 1.8)
Legacy vector clipboard 旧版矢量剪贴板
Legacy (version 7) 旧版 (版本 7)
Transparent Images: 透明图像:
Flatten 拼合
Omit 省略
Legacy .ai Options 旧版 .ai 选项
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. 旧版 .ai 文件只包含一个画板:勾选的每个画板都会存储为单独的文件。
{count} of {total} artboards {total} 个画板中的 {count} 个
Check an artboard to save. 请勾选要存储的画板。
PDF-compatible 与 PDF 兼容
Legacy vector clipboard (no transparency) 旧版矢量剪贴板 (无透明度)
Less 较少
Letter 信纸
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -1455,6 +1455,15 @@
Left: 左:
Legacy Macintosh RGB (Gamma 1.8) 舊版 Macintosh RGB (Gamma 1.8)
Legacy vector clipboard 舊版向量剪貼簿
Legacy (version 7) 舊版 (版本 7)
Transparent Images: 透明影像:
Flatten 平面化
Omit 略過
Legacy .ai Options 舊版 .ai 選項
A legacy .ai file holds one artboard: each artboard you check is saved as a separate file. 舊版 .ai 檔案只包含一個工作區域:勾選的每個工作區域都會儲存為獨立的檔案。
{count} of {total} artboards {total} 個工作區域中的 {count} 個
Check an artboard to save. 請勾選要儲存的工作區域。
PDF-compatible 與 PDF 相容
Legacy vector clipboard (no transparency) 舊版向量剪貼簿 (無透明度)
Less 較少
Letter Letter
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5 -1
View File
@@ -412,7 +412,11 @@ pub fn save(app: &mut VectorcraftApp, mode: SaveMode, p: &Value, ask_options: bo
}
if ask && asks_options(mode, chosen.format) {
let path = chosen.path.clone().unwrap_or_default();
return ask_format_options(app, mode, chosen.format, &path);
let r = ask_format_options(app, mode, chosen.format, &path);
if let Some(d) = app.ui.dialog.as_mut() {
d.fields.insert(crate::dialogs::AFTER_PANEL.into(), json!(true));
}
return r;
}
write_plan(app, chosen)
}
+17 -4
View File
@@ -89,13 +89,26 @@ fn flat_fill(ui: &Ui, rect: Rect, resp: &Response) {
/// Blue call-to-action pill (dialog OK/Create).
pub fn primary_button(ui: &mut Ui, text: &str) -> Response {
primary_button_enabled(ui, text, true)
}
/// [`primary_button`], greyed out and not clickable unless `enabled`.
pub fn primary_button_enabled(ui: &mut Ui, text: &str, enabled: bool) -> Response {
let t = Tokens::get(ui.ctx());
let galley = ui.painter().layout_no_wrap(tl!(text).to_string(), theme::semibold(12.5), Color32::WHITE);
let fg = if enabled { Color32::WHITE } else { t.text_disabled };
let galley = ui.painter().layout_no_wrap(tl!(text).to_string(), theme::semibold(12.5), fg);
let w = galley.size().x + 32.0;
let (rect, resp) = ui.allocate_exact_size(vec2(w.max(72.0), 28.0), Sense::click());
let bg = if resp.hovered() { t.accent } else { t.accent_strong };
let (rect, resp) = ui.allocate_exact_size(vec2(w.max(72.0), 28.0), if enabled { Sense::click() } else { Sense::hover() });
let bg = match (enabled, resp.hovered()) {
(false, _) => t.input,
(true, true) => t.accent,
(true, false) => t.accent_strong,
};
ui.painter().rect_filled(rect, CornerRadius::same(14), bg);
ui.painter().galley(rect.center() - galley.size() / 2.0, galley, Color32::WHITE);
if !enabled {
ui.painter().rect_stroke(rect, CornerRadius::same(14), egui::Stroke::new(1.0, t.button_border), egui::StrokeKind::Inside);
}
ui.painter().galley(rect.center() - galley.size() / 2.0, galley, fg);
resp
}