mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 12:13:19 +00:00
Merge pull request #5950 from mockydevs/feat/progress-indicator-5930
Opening a slow file shows a progress window with Cancel and Continue in Background
This commit is contained in:
@@ -550,6 +550,7 @@ impl VectorcraftApp {
|
||||
export_progress::show(self, &ctx);
|
||||
plugin_runs::show(self, &ctx);
|
||||
trace_runs::show(self, &ctx);
|
||||
opening::show(self, &ctx);
|
||||
palette::show(self, &ctx);
|
||||
if self.custom_titlebar {
|
||||
titlebar::resize_zones(ui);
|
||||
|
||||
@@ -135,6 +135,39 @@ pub fn busy(ui: &mut egui::Ui, what: &str) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Seconds the moving segment of an [`indeterminate`] bar takes to cross the track once.
|
||||
const SWEEP: f64 = 1.6;
|
||||
|
||||
/// The share of the track an [`indeterminate`] bar's segment covers.
|
||||
const SEGMENT: f32 = 0.3;
|
||||
|
||||
/// Where the segment of an [`indeterminate`] bar is at `time` seconds: its left and right ends as
|
||||
/// shares of the track, clipped to it (it enters at the left and leaves at the right).
|
||||
pub fn sweep_at(time: f64) -> (f32, f32) {
|
||||
let phase = if time.is_finite() { (time / SWEEP).rem_euclid(1.0) as f32 } else { 0.0 };
|
||||
let left = phase * (1.0 + SEGMENT) - SEGMENT;
|
||||
(left.max(0.0), (left + SEGMENT).min(1.0))
|
||||
}
|
||||
|
||||
/// Work whose progress isn't known, in a [`window`]: `what` it does, in the UI language, over a
|
||||
/// bar whose segment keeps sweeping across it while the window shows.
|
||||
pub fn indeterminate(ui: &mut egui::Ui, what: &str) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
ui.label(egui::RichText::new(what).color(t.text_dim));
|
||||
ui.add_space(4.0);
|
||||
let (rect, response) = ui.allocate_exact_size(egui::vec2(360.0, 6.0), egui::Sense::hover());
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::ProgressIndicator, true, what));
|
||||
let radius = egui::CornerRadius::same(3);
|
||||
ui.painter().rect_filled(rect, radius, ui.visuals().extreme_bg_color);
|
||||
let (from, to) = sweep_at(ui.input(|i| i.time));
|
||||
if to > from {
|
||||
let x = |share: f32| rect.left() + rect.width() * share;
|
||||
let segment = egui::Rect::from_min_max(egui::pos2(x(from), rect.top()), egui::pos2(x(to), rect.bottom()));
|
||||
ui.painter().rect_filled(segment, radius, t.accent);
|
||||
}
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -125,6 +125,8 @@ fn run_ui_part1(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<
|
||||
"file.reveal" => io::reveal(app),
|
||||
"file.cancelExport" => Ok(io::cancel_exports(app)),
|
||||
"file.exportInBackground" => Ok(io::hide_exports(app)),
|
||||
"file.cancelOpen" => Ok(crate::opening::cancel_all(app)),
|
||||
"file.openInBackground" => Ok(crate::opening::hide_all(app)),
|
||||
id if id.starts_with("file.openRecent") => match recent_slot(app, id).cloned() {
|
||||
Some(path) => io::open_reporting(app, &path),
|
||||
None => Err("no such recent file".into()),
|
||||
|
||||
@@ -202,6 +202,8 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool {
|
||||
id if let Some(b) = crate::panels::align::button(id) => enabled(app, b.command()),
|
||||
"file.reveal" => app.services.reveal.is_some() && app.session.active().is_some_and(|d| d.path.is_some()),
|
||||
"file.cancelExport" | "file.exportInBackground" => app.background.jobs.iter().any(|j| j.progress.is_some() && !j.cancelled()),
|
||||
"file.cancelOpen" => !app.opening.pending.is_empty(),
|
||||
"file.openInBackground" => crate::opening::any_shown(app),
|
||||
"plugin.cancelRuns" => crate::plugin_runs::any_running(app),
|
||||
"plugin.hideRuns" => crate::plugin_runs::any_shown(app),
|
||||
"imageTrace.cancelRuns" => crate::trace_runs::any_running(app),
|
||||
|
||||
@@ -1187,6 +1187,18 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"",
|
||||
"{} close the export progress dialog; the exports go on, shown in the status bar → {hidden: n}",
|
||||
),
|
||||
(
|
||||
"file.cancelOpen",
|
||||
"Cancel Opening",
|
||||
"",
|
||||
"{} stop opening every file being read in the background (its progress window's Cancel): the files don't open → {cancelled: n}",
|
||||
),
|
||||
(
|
||||
"file.openInBackground",
|
||||
"Continue in Background",
|
||||
"",
|
||||
"{} close the progress window of the files being opened; they go on being read, shown in the status bar → {hidden: n}",
|
||||
),
|
||||
(
|
||||
"ui.dxfOptionsDialog",
|
||||
"DXF Options Dialog",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
//! Opening documents off the UI thread (desktop): a file the user asks to open (File › Open, Open
|
||||
//! Recent, the Home screen, files handed to the app) is decoded on a thread of its own, which also
|
||||
//! reads its linked files and lays out its type, while the status bar shows it, and opens in a new tab
|
||||
//! once it is ready, as it would at once: the same notes, recent files and errors. On wasm (no
|
||||
//! threads) and in unit tests it opens at once ([`available`]).
|
||||
//! once it is ready, as it would at once: the same notes, recent files and errors. A file that takes
|
||||
//! longer than [`DELAY`] shows a progress window ([`show`]): its name, a bar that moves while it is
|
||||
//! read (the reader counts no steps), Cancel (also Esc, `file.cancelOpen`: the file doesn't open)
|
||||
//! and Continue in Background (`file.openInBackground`: it goes on, shown in the status bar). On
|
||||
//! wasm (no threads) and in unit tests it opens at once ([`available`]).
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::{Receiver, TryRecvError, channel};
|
||||
@@ -12,9 +15,13 @@ use vectorcraft_engine::cmd::fileio::{self, ReadFile};
|
||||
use vectorcraft_engine::file_access::ScopeHandle;
|
||||
|
||||
use crate::VectorcraftApp;
|
||||
use crate::export_progress::{self, Pressed};
|
||||
|
||||
pub use crate::export_progress::DELAY;
|
||||
|
||||
/// A file being read.
|
||||
pub struct Pending {
|
||||
id: u64,
|
||||
/// Where it is.
|
||||
pub path: String,
|
||||
/// What the status bar shows ("Opening Poster.vectorcraft").
|
||||
@@ -23,6 +30,8 @@ pub struct Pending {
|
||||
/// The automation roots in force where the open started: the document is installed under
|
||||
/// them too (its links are read as it opens).
|
||||
scope: ScopeHandle,
|
||||
/// Continue in Background: the progress window no longer shows it.
|
||||
pub hidden: bool,
|
||||
}
|
||||
|
||||
/// The files being read.
|
||||
@@ -32,6 +41,7 @@ pub struct Opening {
|
||||
/// Open documents asked for again while the active one couldn't change: their tabs come to
|
||||
/// the front once it can ([`may_switch`]), each in the file access scope it was asked in.
|
||||
pub activate: Vec<(String, ScopeHandle)>,
|
||||
next_id: u64,
|
||||
}
|
||||
|
||||
/// Is a long operation running that should show the busy cursor and status spinner (background
|
||||
@@ -81,7 +91,9 @@ pub fn start(app: &mut VectorcraftApp, path: &str, bytes: Vec<u8>) -> Result<Val
|
||||
return Err(Arc::try_unwrap(bytes).unwrap_or_else(|b| b.to_vec()));
|
||||
}
|
||||
let label = format!("Opening {}", fileio::file_name(path));
|
||||
app.opening.pending.push(Pending { path: path.to_string(), label: label.clone(), done, scope: opener });
|
||||
app.opening.next_id += 1;
|
||||
let id = app.opening.next_id;
|
||||
app.opening.pending.push(Pending { id, path: path.to_string(), label: label.clone(), done, scope: opener, hidden: false });
|
||||
let status = format!("{label}…");
|
||||
app.status(status.clone());
|
||||
Ok(json!({ "background": true, "status": status }))
|
||||
@@ -139,6 +151,75 @@ pub fn poll(app: &mut VectorcraftApp) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The progress window's Cancel: the files being read don't open (their threads finish reading
|
||||
/// and the result is dropped) → `{cancelled: n}`.
|
||||
pub fn cancel_all(app: &mut VectorcraftApp) -> Value {
|
||||
let cancelled = std::mem::take(&mut app.opening.pending);
|
||||
for p in &cancelled {
|
||||
// The status bar no longer says it is opening.
|
||||
if app.ui.status == format!("{}…", p.label) {
|
||||
app.ui.status.clear();
|
||||
}
|
||||
}
|
||||
json!({ "cancelled": cancelled.len() })
|
||||
}
|
||||
|
||||
/// The progress window's Continue in Background: the files go on being read, shown in the status
|
||||
/// bar only → `{hidden: n}`.
|
||||
pub fn hide_all(app: &mut VectorcraftApp) -> Value {
|
||||
let mut n = 0;
|
||||
for p in app.opening.pending.iter_mut().filter(|p| !p.hidden) {
|
||||
p.hidden = true;
|
||||
n += 1;
|
||||
}
|
||||
json!({ "hidden": n })
|
||||
}
|
||||
|
||||
/// Is a file being read that the progress window shows?
|
||||
pub fn any_shown(app: &VectorcraftApp) -> bool {
|
||||
app.opening.pending.iter().any(|p| !p.hidden)
|
||||
}
|
||||
|
||||
/// The progress window of the first file being read it shows, once that has been read for
|
||||
/// [`DELAY`]: a quick open doesn't flash it. Not while a dialog or another progress window (an
|
||||
/// export, a heavy plug-in filter, an Image Trace) is open: never two modals at once.
|
||||
pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
let shown: Vec<&Pending> = app.opening.pending.iter().filter(|p| !p.hidden).collect();
|
||||
let Some(first) = shown.first() else { return };
|
||||
let now = ctx.input(|i| i.time);
|
||||
let since = ctx.data_mut(|m| {
|
||||
let seen = m.get_temp_mut_or_insert_with(egui::Id::unique("opening-seen"), || (first.id, now));
|
||||
if seen.0 != first.id {
|
||||
*seen = (first.id, now);
|
||||
}
|
||||
seen.1
|
||||
});
|
||||
let exporting = app.background.jobs.iter().any(|j| j.progress.is_some() && !j.hidden && !j.cancelled());
|
||||
let other = exporting || crate::plugin_runs::any_shown(app) || crate::trace_runs::any_shown(app);
|
||||
if now - since < DELAY || app.ui.dialog.is_some() || other {
|
||||
return;
|
||||
}
|
||||
let heading = format!("{}…", crate::i18n::msg(&first.label));
|
||||
let more = shown.len() - 1;
|
||||
let t = crate::theme::Tokens::get(ctx);
|
||||
let pressed = export_progress::window(ctx, "opening-progress", &heading, |ui| {
|
||||
export_progress::indeterminate(ui, tl!("Reading the file…"));
|
||||
if more > 0 {
|
||||
ui.add_space(6.0);
|
||||
let queued = crate::i18n::tn(more as u64, "{n} more file waiting", "{n} more files waiting");
|
||||
ui.label(egui::RichText::new(queued).size(11.5).color(t.text_dim));
|
||||
}
|
||||
});
|
||||
let id = match pressed {
|
||||
Some(Pressed::Cancel) => "file.cancelOpen",
|
||||
Some(Pressed::Hide) => "file.openInBackground",
|
||||
None => return,
|
||||
};
|
||||
if let Err(e) = app.run(id, json!({})) {
|
||||
app.status(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a file read off the UI thread, as [`crate::io::open_reporting`] opens one read at once.
|
||||
fn finish(app: &mut VectorcraftApp, path: &str, r: Result<ReadFile, String>) {
|
||||
let opened = r.and_then(|file| fileio::open_read(&mut app.session, file).map_err(|e| e.to_string()));
|
||||
|
||||
@@ -363,3 +363,90 @@ fn a_file_that_opens_and_asks_holds_back_the_next_one_ready() {
|
||||
assert_eq!(app.session.doc().unwrap().path.as_deref(), Some("/art/Asks.vectorcraft"), "the dialog's document stays active");
|
||||
assert_eq!(app.opening.pending.len(), 1, "the next file waits");
|
||||
}
|
||||
|
||||
/// Draws the opening progress window at `time` seconds → the texts it drew.
|
||||
fn progress(app: &mut VectorcraftApp, ctx: &egui::Context, time: f64) -> String {
|
||||
let mut out = ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| show(app, ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
let mut texts = String::new();
|
||||
for shape in out.shapes {
|
||||
if let egui::epaint::Shape::Text(t) = shape.shape {
|
||||
texts.push_str(t.galley.text());
|
||||
texts.push('\n');
|
||||
}
|
||||
}
|
||||
texts
|
||||
}
|
||||
|
||||
/// A document's bytes, `title` naming it.
|
||||
fn bytes(title: &str) -> Vec<u8> {
|
||||
let mut d = vectorcraft_doc::Document::new(120.0, 80.0);
|
||||
d.title = title.into();
|
||||
vectorcraft_format::save(&d, false)
|
||||
}
|
||||
|
||||
/// #5930: a file that takes a while to open shows a progress window with its name and a moving
|
||||
/// bar, after [`DELAY`] so a quick open doesn't flash it; a second file says it waits, and the
|
||||
/// window waits for a dialog to close.
|
||||
#[test]
|
||||
fn a_file_that_takes_a_while_to_open_shows_a_progress_window() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Services::default());
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
assert_eq!(progress(&mut app, &ctx, 0.0), "", "nothing opening");
|
||||
start(&mut app, "/art/Big.vectorcraft", bytes("Big")).unwrap();
|
||||
assert_eq!(progress(&mut app, &ctx, 1.0), "", "not at once");
|
||||
progress(&mut app, &ctx, 1.0 + DELAY + 0.05); // a new window measures itself unseen first
|
||||
let texts = progress(&mut app, &ctx, 1.0 + DELAY + 0.1);
|
||||
for want in ["Opening Big.vectorcraft…", "Reading the file…", "Cancel", "Continue in Background"] {
|
||||
assert!(texts.contains(want), "{want} in {texts}");
|
||||
}
|
||||
assert!(!texts.contains("waiting"), "{texts}");
|
||||
start(&mut app, "/art/Next.vectorcraft", bytes("Next")).unwrap();
|
||||
let texts = progress(&mut app, &ctx, 2.0);
|
||||
assert!(texts.contains("Opening Big.vectorcraft…") && texts.contains("1 more file waiting"), "{texts}");
|
||||
app.ui.dialog = Some(crate::state::Dialog::new(crate::dialogs::save_options::KIND, json!({})));
|
||||
assert_eq!(progress(&mut app, &ctx, 3.0), "", "never two modals at once");
|
||||
app.ui.dialog = None;
|
||||
// Continue in Background: the window closes, the files still open.
|
||||
let before = app.session.documents().len();
|
||||
assert!(crate::menus::enabled(&app, "file.openInBackground"));
|
||||
assert_eq!(app.run("file.openInBackground", json!({})).unwrap()["hidden"], 2);
|
||||
assert_eq!(progress(&mut app, &ctx, 4.0), "");
|
||||
assert!(!crate::menus::enabled(&app, "file.openInBackground"));
|
||||
assert!(long_operation_busy(&app), "the status bar still shows them");
|
||||
open_ready(&mut app);
|
||||
assert_eq!(app.session.documents().len(), before + 2);
|
||||
}
|
||||
|
||||
/// #5930: the progress window's Cancel (`file.cancelOpen`): the file doesn't open, even once it
|
||||
/// has been read, and the status bar no longer says it is opening.
|
||||
#[test]
|
||||
fn cancelling_an_open_opens_nothing() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Services::default());
|
||||
assert!(!crate::menus::enabled(&app, "file.cancelOpen"));
|
||||
let before = app.session.documents().len();
|
||||
start(&mut app, "/art/Big.vectorcraft", bytes("Big")).unwrap();
|
||||
assert!(crate::menus::enabled(&app, "file.cancelOpen"));
|
||||
assert_eq!(app.run("file.cancelOpen", json!({})).unwrap()["cancelled"], 1);
|
||||
assert!(app.opening.pending.is_empty() && !long_operation_busy(&app));
|
||||
assert!(!app.ui.status.starts_with("Opening"), "{}", app.ui.status);
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
poll(&mut app);
|
||||
assert_eq!(app.session.documents().len(), before);
|
||||
assert_ne!(app.ui.recent_files.first().map(String::as_str), Some("/art/Big.vectorcraft"));
|
||||
}
|
||||
|
||||
/// The moving bar's segment enters at the left, crosses the track and leaves at the right, and a
|
||||
/// time that isn't finite puts it at the start.
|
||||
#[test]
|
||||
fn the_indeterminate_bar_sweeps_across_its_track() {
|
||||
use crate::export_progress::sweep_at;
|
||||
assert_eq!(sweep_at(0.0), (0.0, 0.0));
|
||||
let (from, to) = sweep_at(0.8);
|
||||
assert!(from > 0.0 && to < 1.0 && to > from, "{from} {to}");
|
||||
let (from, to) = sweep_at(1.55);
|
||||
assert!(from > 0.6 && to == 1.0, "{from} {to}");
|
||||
assert_eq!(sweep_at(f64::NAN), (0.0, 0.0));
|
||||
assert_eq!(sweep_at(-0.8), sweep_at(0.8));
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
# Gaps
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
| 2026-11-06 | minor | Opening a file that takes longer than 0.4 s shows a progress window: the file name, a moving bar (reading reports no steps), how many more files wait, Cancel (Esc; the file doesn't open) and Continue in Background (`file.cancelOpen`, `file.openInBackground`); it never opens over another dialog or progress window (part of #5930); scores and estimates unchanged |
|
||||
Raster rendering fits and preflattens curved stroke outlines more tightly only below four times the raster tolerance, retaining thin-band coverage without changing stroke weight or vector export geometry. Current cached character/object outlines, cropped long strokes, gradient clips and effect rasters share the helper; cropped-region reuse and guarded flattening remain. The unchanged `fractional_curved_strokes_retain_coverage_between_notches` regression covers fractional weights, rotation, phase, opacity and f32-rounded endpoints. This is a quality/cost tradeoff, not a speed claim. Scores and estimates unchanged.
|
||||
|
||||
**PR #3273 revision — 2026-11-04:** Thin curved stroke coverage ported to the current raster-only cache, cropped-long-stroke and effect paths with guarded flattening; vector export geometry unchanged. Scores and estimates unchanged.
|
||||
@@ -437,7 +438,7 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall
|
||||
Estimate unchanged.
|
||||
- **Done (2026-10-21, #394):** General › Constrain Angle turns the arrow keys' axes (guides stay on the page's), holds Shift drags of the Selection, Direct Selection, Free Transform, Slice Selection, Reshape and Measure tools and the Pen's handles to the turned axes, and new rectangles, rounded rectangles, ellipses, grids, point and area type, Shift-drawn polygons and stars and the shape size dialogs are drawn along it with their bounding box turned; 0° and whole turns are exactly as before, quarter turns exact (<https://helpx.adobe.com/illustrator/desktop/manage-objects/arrange-objects/rotate-objects-by-specific-angles.html>). Arcs, spirals, flares and the Pen's new paths are not turned yet. Estimate unchanged.
|
||||
- **Done (2026-10-21, #394):** General › Constrain Angle turns the arrow keys' axes (guides stay on the page's), holds Shift drags of the Selection, Direct Selection, Free Transform, Slice Selection, Reshape and Measure tools and the Pen's handles to the turned axes, and new rectangles, rounded rectangles, ellipses, grids, point and area type, Shift-drawn polygons and stars and the shape size dialogs are drawn along it with their bounding box turned; 0° and whole turns are exactly as before, quarter turns exact (<https://helpx.adobe.com/illustrator/desktop/manage-objects/arrange-objects/rotate-objects-by-specific-angles.html>). Arcs, spirals, flares and the Pen's new paths are not turned yet. Estimate unchanged. **2026-10-24:** General › Corner Radius is the Rounded Rectangle tool's radius: the tool takes the preference when chosen and when it changes while the tool is active (or lent a selection tool for a drag), and the radius ↑/↓ set while drawing one, or a Tool Options value, becomes the preference, clamped to its 0–1296 pt range (<https://helpx.adobe.com/illustrator/using/drawing-simple-lines-shapes.html>). Estimate unchanged.
|
||||
- **Done (2026-10-17):** Background Export shows its progress in a dialog with Cancel and Continue in Background (#2564), on the shared cancel token (#2546); a cancelled export writes no file. Save As, Export for Screens and Export Selection don't show it yet. Estimate unchanged.
|
||||
- **Done (2026-10-17):** Background Export shows its progress in a dialog with Cancel and Continue in Background (#2564), on the shared cancel token (#2546); a cancelled export writes no file. Save As, Export for Screens and Export Selection don't show it yet. Estimate unchanged. **2026-11-06:** opening a file that takes longer than 0.4 s shows a progress window too, with a moving bar, Cancel and Continue in Background (part of #5930); Save and Place still show only the status bar's spinner. Estimate unchanged.
|
||||
- **Done:** quick Image Trace presets trace at once and leave the panel as it was; the Image Trace button (Control bar, Properties) opens the controls with Threshold and Advanced (Paths, Corners, Noise), as the public documentation describes (<https://helpx.adobe.com/illustrator/using/image-trace-results-optimization.html>). Other context-specific Properties gaps remain.
|
||||
- **Done (2026-10-31, #394):** Units › East Asian Type is read, and Q and Ha are units. 1 Q and 1 Ha are each a
|
||||
quarter of a millimetre (0.709 pt), the Japanese typesetting units: the [Illustrator Scripting Guide's
|
||||
|
||||
+3
-1
@@ -1,9 +1,10 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Direct Selection: with Snap to Grid, a dragged straight segment lands its anchors on the grid, #3835; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (opening a slow file shows a progress window with Cancel and Continue in Background, part of #5930; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
| 2026-11-06 | minor | Opening a file that takes longer than 0.4 s shows a progress window: the file name, a moving bar (reading reports no steps), how many more files wait, Cancel (Esc; the file doesn't open) and Continue in Background (`file.cancelOpen`, `file.openInBackground`); it never opens over another dialog or progress window (part of #5930); scores and estimates unchanged |
|
||||
Raster rendering fits and preflattens curved stroke outlines more tightly only below four times the raster tolerance, retaining thin-band coverage without changing stroke weight or vector export geometry. Current cached character/object outlines, cropped long strokes, gradient clips and effect rasters share the helper; cropped-region reuse and guarded flattening remain. The unchanged `fractional_curved_strokes_retain_coverage_between_notches` regression covers fractional weights, rotation, phase, opacity and f32-rounded endpoints. This is a quality/cost tradeoff, not a speed claim. Scores and estimates unchanged.
|
||||
|
||||
**PR #3273 revision — 2026-11-04:** Thin curved stroke coverage ported to the current raster-only cache, cropped-long-stroke and effect paths with guarded flattening; vector export geometry unchanged. Scores and estimates unchanged.
|
||||
@@ -397,6 +398,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
|
||||
| Gradient panel: delete a stop | done | Delete Stop at the right end of the ramp deletes the selected stop, and a stop dragged down off the ramp is deleted on release, never below two stops, one undo step each; [Gradient panel options](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-gradients/gradient-tool-and-panel.html); `panels::gradient` test |
|
||||
| SVG Interactivity, Retype, Comments, Version History panels | missing | the last two are cloud features (out of scope) |
|
||||
| Workspaces (save, reset, switch); each remembers the layout changes made in it across switches and restarts until Reset <name> | done | `UiState::workspace_changes` |
|
||||
| Opening a file: after 0.4 s a progress window with the file name, a moving bar (reading counts no steps), how many more files wait, Cancel (Esc: the file doesn't open) and Continue in Background; never over another dialog or progress window | done | #5930; `opening::show`, `file.cancelOpen`, `file.openInBackground`. Save, Place and Close still show only the status bar's spinner (Place and Save run off the UI thread; Close is immediate) |
|
||||
| Background Export: a progress dialog with the file name, the artboard count and a bar (busy for one artboard and for formats made in one go), Cancel (Esc) and Continue in Background; it opens after 0.4 s and never over another dialog. A cancelled export writes no file | done | #2564; on the shared `vectorcraft_doc::cancel` token (#2546), so a cancelled render keeps no half-drawn pattern tile or effect raster. The bar moves between artboards, not within one. Save As, Export for Screens and Export Selection don't show it yet |
|
||||
| Swatch, graphic style and symbol library panels: each library opens in a panel of its own, several at once, that floats, groups, docks and closes like any panel; opening an open library brings its panel forward; Previous / Next Library change the library in place; the panel menu's Persistent reopens the panel at startup with its view and find field (a library loaded from a file is read again; when its file is missing the panel reopens showing the file's path with Locate Library…, and the status bar names the file) | done | #2603; panel ids `library:<kind>:<id>`, `ui.libraryPanel {panel, library?, view?, showFind?, persistent?}`, `ui.libraryPanel.locate {panel, path?}`; the library panels have no sort option, as before |
|
||||
| DXF export/import preserves intended curve closure | done | #1299; R13–2018 closed SPLINE bit set/read; an open curve ending at its start stays open; knots and control points unchanged; native CIRCLE/ARC export and CNC preset remain pending |
|
||||
|
||||
Reference in New Issue
Block a user