Merge pull request #1018: Pr/layout switch notice Draw a notice for one frame before rebuilding a layout

This commit is contained in:
Hakan Seven committed 2026-09-05 19:28:31 +03:00
commit faafdb14bf
6 files changed
+62 -1

No files matched your search

+1
View File
@@ -167,6 +167,7 @@ clipboard =
.paste-to-original-coordinates = Επικόλληση στις αρχικές συντεταγμένες
viewport =
.preparing-layout = Προετοιμασία διάταξης…
.split-vertical = Κατακόρυφος διαχωρισμός θύρας προβολής
.split-horizontal = Οριζόντιος διαχωρισμός θύρας προβολής
.move = Μετακίνηση θύρας προβολής
+1
View File
@@ -167,6 +167,7 @@ clipboard =
.paste-to-original-coordinates = Paste to Original Coordinates
viewport =
.preparing-layout = Preparing layout…
.split-vertical = Split Viewport Vertically
.split-horizontal = Split Viewport Horizontally
.move = Move Viewport
+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(),
+6
View File
@@ -3045,6 +3045,12 @@ impl OpenCADStudio {
Task::none()
}
Message::LayoutSettled => {
// The notice frame has been presented, so the compositor has
// had its answer. Let the next frame build the real thing.
self.layout_settling = false;
Task::none()
}
Message::GripDwellTick => {
let i = self.active_tab;
// Reuse the move-time logic — `p` is the last cursor
+4
View File
@@ -5534,6 +5534,10 @@ impl OpenCADStudio {
.layers
.sync_with_viewports(&doc_layers, vp_info);
let layers_ms = perf_phase.elapsed().as_secs_f64() * 1000.0;
// The switch is cheap; the frame that follows it is not. Draw a notice
// for one frame first, so the compositor gets an answer before the main
// thread disappears into rebuilding the wire set.
self.layout_settling = true;
if perf {
crate::perf_record!(
"[perf] layout-switch from={} to={} total={:.2}ms sync={:.2}ms switch={:.2}ms restore={:.2}ms layers={:.2}ms epoch={}",
+37 -1
View File
@@ -272,7 +272,33 @@ impl OpenCADStudio {
// viewport draws the layout's own geometry (white sheet + entities +
// borders) and the floating content viewports blit on top.
mark("viewport_3d");
let viewport_3d: Element<'_, Message> = if tab.is_start {
let viewport_3d: Element<'_, Message> = if self.layout_settling && !tab.is_start {
// One cheap frame between the switch and the rebuild. No shader
// widget means no scene primitive, so `prepare` has nothing to do
// and the event loop turns — which is what stops the compositor
// deciding the application has stopped responding.
let bg = crosshair_background(tab, is_paper);
let lum = 0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2];
let ink = if lum > 0.5 {
Color::from_rgba(0.0, 0.0, 0.0, 0.55)
} else {
Color::from_rgba(1.0, 1.0, 1.0, 0.55)
};
container(
text(crate::tr!("viewport", "preparing-layout"))
.size(15)
.color(ink),
)
.width(Fill)
.height(Fill)
.center_x(Fill)
.center_y(Fill)
.style(move |_| container::Style {
background: Some(Color::from_rgba(bg[0], bg[1], bg[2], bg[3]).into()),
..container::Style::default()
})
.into()
} else if tab.is_start {
start_page_view(
&self.patrons,
&self.videos,
@@ -2253,6 +2279,15 @@ impl OpenCADStudio {
// the mouse perfectly still — `ViewportMove` alone would never
// fire again. Auto-stops once the hover clears or the popup is
// already open.
// One frame after a layout switch, then stop. `window::frames` fires
// once a frame has actually been presented, which is the guarantee this
// needs: the notice is on screen and the compositor has been answered
// before the expensive frame starts.
let layout_settle = if self.layout_settling {
window::frames().map(|_| Message::LayoutSettled)
} else {
Subscription::none()
};
let grip_dwell = if self.grip_hover.is_some() && self.grip_popup.is_none() {
window::frames().map(|_| Message::GripDwellTick)
} else {
@@ -2478,6 +2513,7 @@ impl OpenCADStudio {
control,
frames,
history_tick,
layout_settle,
grip_dwell,
hover_dwell,
nav_settle,