feat: draw a notice for one frame before rebuilding a layout

Switching to a Layout stops the main thread long enough that the
compositor decides the application has stopped responding and offers to
kill it. The switch itself is not the problem — `layout-switch` reports
about 10 ms — it is the frame after it, which rebuilds the whole wire set.

That frame now has a cheap one in front of it. `layout_settling` is set
when the switch takes, and while it holds, the viewport is replaced by a
centered "Preparing layout…" on the canvas background. No shader widget
means no scene primitive, so `prepare` has nothing to do and the frame
costs nothing. A `window::frames` subscription clears the flag, which is
the guarantee this needs: that callback runs once a frame has actually
been presented, so the notice is on screen and the compositor has been
answered before the expensive frame starts.

The deferral is of the *frame*, not the switch. Two callers depend on the
switch having completed when it returns — one calls `fit_all` immediately
after, another compares `current_layout` — so deferring the switch itself
would have broken both.

This restarts the compositor's timer immediately before the slow frame and
replaces a stale window with something that says what is happening. It
does not make the slow frame fast: if the rebuild still exceeds the
compositor's timeout the dialog can return, and the durable fix is the
2 s `memo_miss` behind it, which is a separate change.

Only `en-US` is validated against at build time; Greek is filled in too
since it is in use here, and the rest fall back until translated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-09-05 18:53:02 +03:00
1 parent d9a1d55f2e
commit 09838be9ec
6 files changed
+62 -1

No files matched your search

+13
View File
@@ -1055,6 +1055,15 @@ pub(super) struct OpenCADStudio {
/// `Some` while a CAD file is loading — drives the modal overlay.
/// Cleared when the load finishes, errors, or the user cancels.
pub(super) opening: Option<OpenProgress>,
/// Set the moment a layout switch changes `current_layout`, cleared on the
/// next presented frame.
///
/// The switch itself costs about 10 ms; the frame after it rebuilds the
/// whole wire set and can block the main thread for seconds. While this is
/// set the viewport is replaced by a notice, so that frame is cheap — which
/// gives the compositor an answer, and the user something other than a
/// stale window, immediately before the expensive one.
pub(super) layout_settling: bool,
open_job_serial: u64,
/// Last repair or failed-open report shown in the recovery modal.
recovery_report: Option<crate::io::recovery::RecoveryReport>,
@@ -2283,6 +2292,9 @@ pub enum Message {
/// Timer pulse while the cursor is dwelling on a grip; drives the
/// dwell-to-popup transition without requiring further mouse motion.
GripDwellTick,
/// One frame has been presented since a layout switch; the next may do the
/// expensive rebuild.
LayoutSettled,
/// Timer pulse while a rollover hit-test is queued; fires when the
/// cursor has been still long enough to safely run the pick.
HoverDwellTick,
@@ -3484,6 +3496,7 @@ impl OpenCADStudio {
print_all_plot_window_prev: None,
print_all_plot_setup_prev: None,
opening: None,
layout_settling: false,
open_job_serial: 0,
recovery_report: None,
pending_opens: std::collections::VecDeque::new(),