mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
G1: busy cursor during background open, save and export
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
bflatastic
co-authored by
Cursor
parent
aa9c70a7b1
commit
49f7f5d7d8
@@ -392,6 +392,16 @@ shown_when = [
|
||||
hotspot = "The centre."
|
||||
designer = false
|
||||
|
||||
[[cursor]]
|
||||
id = "Busy"
|
||||
group = "Navigation"
|
||||
shown_when = [
|
||||
"A file is opening off the UI thread.",
|
||||
"A background save or export is in progress (the status bar shows its name).",
|
||||
]
|
||||
hotspot = "The centre."
|
||||
designer = false
|
||||
|
||||
# ── Colour and gradient ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[[cursor]]
|
||||
|
||||
@@ -494,6 +494,8 @@ pub enum Cursor {
|
||||
ZoomOut,
|
||||
Eyedropper,
|
||||
NotAllowed,
|
||||
/// A long operation (opening a file, a background save or export): the OS wait cursor.
|
||||
Busy,
|
||||
/// Over the gradient annotator's bar: a click adds a stop.
|
||||
AddStop,
|
||||
/// A gradient stop dragged off the bar: releasing deletes it.
|
||||
|
||||
@@ -8,6 +8,7 @@ use vectorcraft_doc::{Node, NodeKind, SymbolBoxes, Unit};
|
||||
use vectorcraft_geom::{Affine, BezPath, PathEl, Point, Rect};
|
||||
use vectorcraft_tools::{Cursor, Mods, Overlay, PointerEvent, PointerKind};
|
||||
|
||||
use crate::opening;
|
||||
use crate::state::View;
|
||||
use crate::theme::{self, Tokens};
|
||||
use crate::{CacheKey, VectorcraftApp, now_ms, widgets};
|
||||
@@ -536,6 +537,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
artboard_name_editor(app, ui, &xf);
|
||||
// Cursor.
|
||||
if resp.hovered() {
|
||||
if opening::long_operation_busy(app) {
|
||||
ui.ctx().set_cursor_icon(cursor_icon(Cursor::Busy));
|
||||
return;
|
||||
}
|
||||
let m = ui.input(|i| i.modifiers);
|
||||
let space = ui.input(|i| i.key_down(egui::Key::Space));
|
||||
let drag = ui.data(|d| d.get_temp::<Drag>(drag_id()));
|
||||
@@ -638,6 +643,7 @@ fn cursor_icon(c: Cursor) -> egui::CursorIcon {
|
||||
Cursor::ZoomOut => C::ZoomOut,
|
||||
Cursor::Eyedropper => C::Crosshair,
|
||||
Cursor::NotAllowed => C::NotAllowed,
|
||||
Cursor::Busy => C::Progress,
|
||||
Cursor::AddStop => C::Copy,
|
||||
Cursor::RemoveStop => C::NotAllowed,
|
||||
Cursor::Slice => C::Crosshair,
|
||||
|
||||
@@ -797,6 +797,7 @@ mod tests {
|
||||
assert!(pixel(&dropper, hx, hy)[3] > 0, "the tip covers the hotspot at {ppp}");
|
||||
}
|
||||
assert!(rasterize(Cursor::NotAllowed, 1.0).is_none(), "the busy states stay system cursors");
|
||||
assert!(rasterize(Cursor::Busy, 1.0).is_none());
|
||||
assert!(rasterize(Cursor::ScissorsNotAllowed, 1.0).is_none(), "until it's drawn it borrows the system not-allowed cursor");
|
||||
for ppp in [0.0, -1.0, f32::NAN, f32::INFINITY, 1e9] {
|
||||
assert!(rasterize(Cursor::Arrow, ppp).is_none(), "{ppp}");
|
||||
|
||||
@@ -1471,6 +1471,9 @@ impl VectorcraftApp {
|
||||
scrub::end_frame(self, &ctx);
|
||||
native_menu::sync(self, &ctx);
|
||||
self.drop_formats_after_egui_copy(&ctx);
|
||||
if opening::long_operation_busy(self) {
|
||||
ctx.set_cursor_icon(egui::CursorIcon::Progress);
|
||||
}
|
||||
self.perf.frame_ms = now_ms() - t0;
|
||||
let _ = json!(null);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@ pub struct Opening {
|
||||
pub pending: Vec<Pending>,
|
||||
}
|
||||
|
||||
/// Is a long operation running that should show the busy cursor and status spinner (background
|
||||
/// save/export with a label, or a file opening off the UI thread)?
|
||||
pub fn long_operation_busy(app: &VectorcraftApp) -> bool {
|
||||
app.background.jobs.iter().any(|j| !j.label.is_empty() && !j.cancelled()) || !app.opening.pending.is_empty()
|
||||
}
|
||||
|
||||
/// Does opening happen off the UI thread here? Not on wasm (no threads), in unit tests (they look
|
||||
/// at the document right after asking for it), or with `VECTORCRAFT_SYNC_OPEN` set.
|
||||
pub fn available() -> bool {
|
||||
@@ -114,6 +120,46 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::Services;
|
||||
|
||||
#[test]
|
||||
fn long_operation_busy_while_a_file_opens_or_a_background_save_runs() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Services::default());
|
||||
assert!(!long_operation_busy(&app));
|
||||
start(&mut app, "/art/Wait.vectorcraft", vectorcraft_format::save(&vectorcraft_doc::Document::new(10.0, 10.0), false)).unwrap();
|
||||
assert!(long_operation_busy(&app));
|
||||
let t0 = std::time::Instant::now();
|
||||
while long_operation_busy(&app) && t0.elapsed().as_secs() < 30 {
|
||||
poll(&mut app);
|
||||
std::thread::yield_now();
|
||||
}
|
||||
assert!(!long_operation_busy(&app));
|
||||
let (release, held) = std::sync::mpsc::channel::<()>();
|
||||
let services = Services {
|
||||
write_shared: Some(std::sync::Arc::new(|_: &str, _: &[u8]| Ok(()))),
|
||||
..Default::default()
|
||||
};
|
||||
app = VectorcraftApp::new(Session::new(), services);
|
||||
crate::background::run(
|
||||
&mut app,
|
||||
true,
|
||||
"Saving test.vectorcraft".into(),
|
||||
None,
|
||||
move |_| {
|
||||
let _ = held.recv_timeout(std::time::Duration::from_secs(10));
|
||||
Ok(json!({}))
|
||||
},
|
||||
|_, r| r,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(long_operation_busy(&app));
|
||||
let _ = release.send(());
|
||||
let t0 = std::time::Instant::now();
|
||||
while long_operation_busy(&app) && t0.elapsed().as_secs() < 30 {
|
||||
crate::background::poll(&mut app);
|
||||
std::thread::yield_now();
|
||||
}
|
||||
assert!(!long_operation_busy(&app));
|
||||
}
|
||||
|
||||
/// #1694: a file that finishes opening while a dialog is up waits for it to close, so the
|
||||
/// dialog's document stays the active one.
|
||||
#[test]
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall
|
||||
OS cursor display and Caps Lock remain unverified.
|
||||
- **Done (#1251):** Eyedropper source reads are independent of locks and isolation, including Shift/Shift+Alt and gradient stops. Source and selection preservation, unchanged Alt reverse/selection restrictions, hidden art and Undo/Redo have regression coverage. This addresses the reported behavior, with no new full-parity measurement.
|
||||
- **Done (#1253):** adding paint to an unpainted zero-width stroke restores the existing 1 pt default, including new-art defaults and Swap Fill/Stroke. The real Eyedropper inheritance sequence, targeted appearance items, preserved widths and Undo/Redo have regression coverage.
|
||||
- **Missing:** a busy cursor while a long operation runs (a big file opening, a heavy effect): VectorCraft has no busy state to show it yet. Once it has one, its `Cursor` value goes in the cursor list (`crates/tools/src/cursors.toml`).
|
||||
- **Done:** a busy cursor while a file opens off the UI thread or a labelled background save or export runs (`Cursor::Busy` in `crates/tools/src/cursors.toml`). Heavy synchronous effect previews remain open.
|
||||
- **Missing:** Average and Join in one step, with a corner or smooth choice for the joined anchor. `path.average` and `path.join` are separate commands and Join has no such choice (coinciding anchors merge into one since #1533). [ui-parity.md](ui-parity.md) listed "Join with corner/smooth choice" as done until 2026-10-13. About 1–2 h (estimated), inside the estimate below.
|
||||
- **Implemented (#1760):** Effect › Warp has a shared style selector with original previews, orientation buttons and percent sliders. Existing effects retain their stack slot and target. Focused checks passed and original captures were reviewed; optional Properties integration and remembered defaults remain outside this change.
|
||||
- **Done (#2120):** Smart Guides line extension: dragging the end of a straight segment with Direct Selection or Curvature, the point snaps to the segment's own direction at any angle and the guide runs along it from the other end. Inside the estimate below.
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ tools, never checked side by side) isn't captured row by row.
|
||||
| Anchor Point and Curvature tools' own cursors | done | the caret on the anchor it converts; the pen nib with a curve badge |
|
||||
| Free Transform's Cmd/Ctrl side-shear cursor | done | two arrows passing along the side (ShearH, ShearV), instead of the resize arrow across it |
|
||||
| Cursors of their own for Lasso, Magic Wand, the freehand tools, Shaper, Live Paint Bucket, Measure, Print Tiling, the symbolism tools, vertical type, Scissors (over a segment and away), Mesh and Puppet Warp | partial | each tool returns its own cursor and Use Precise Cursors turns them into the crosshair; until they're drawn they borrow the crosshair, the I-beam, the Pen's add cursor or not-allowed |
|
||||
| Busy cursor during long operations | missing | VectorCraft has no busy state yet (gap G1) |
|
||||
| Busy cursor during long operations | done | Background open, save and export show the system progress cursor (gap G1) |
|
||||
| Each anchor snaps to Smart Guides and construction angles | done | `DrawSnap` |
|
||||
| Curvature tool: click/drag points, double-click or Alt-click for a corner, temporary Direct Selection, point conversion | partial | circular point markers without direction handles or duplicate markers, Anchor Size and Hide Edges regression-tested (#1236, in part); curve geometry: each stretch of three points follows the circle through them (handles along its tangent, chord/(3·cos²(α/2)) long, shorter in a turn tighter than 30°), open ends curving in, the same rule for the rubber band, dragged, inserted and converted points (#1236, in part); neighbours of a point dragged on an existing path keep their tangents, and modifiers requested in #975 remain |
|
||||
| Blend spine Anchor Point conversion and handle editing (#2544) | implemented | symmetric handles, independent drag/retraction, visible-handle policy and selection/stacking covered by tool and engine regressions; Windows native canvas checks and screenshots reviewed in PR #2643 |
|
||||
|
||||
Reference in New Issue
Block a user