mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
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:
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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)
|
||||
|
||||
|
@@ -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.
|
@@ -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.
|
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user