fix(modals): hide the auto-height measuring frame so dialogs only appear centered

Auto-height dialogs need one frame to measure their content height before they
can be centered. Previously that first frame was drawn near the top, so a
first-ever open flashed off-center for a frame before snapping to center.

On the measuring frame, lay the content out (so the child's auto-height is still
measured) but clip the whole card to a zero rect and skip the glass panel — the
card draws nothing while the blur backdrop still shows, then it appears already
centered on the next frame. Clip covers both ImGui widgets and hand-drawn
content; layout/measurement is unaffected (verified migrate / about / import-key
still render centered at the settled frame).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 15:09:05 -05:00
parent 1fd794ccc1
commit 5e5f167fb0

View File

@@ -1288,6 +1288,9 @@ inline int SegmentedControl(ImDrawList* dl, ImVec2 origin, float totalW, float h
// carries the active dialog's key from BeginOverlayDialog to EndOverlayDialog (overlays don't nest).
inline std::unordered_map<std::string, float> g_overlayCardHeights;
inline std::string g_overlayCurrentKey;
// Set when BeginOverlayDialog clips the card on an auto-height dialog's measuring frame (so it never
// flashes off-center); EndOverlayDialog pops the clip. Overlays don't nest, so a single flag is fine.
inline bool g_overlayHideFrameClip = false;
// The dark base color for any full-window overlay backdrop (dialog scrims + the shutdown screen),
// so every overlay shares one backdrop tone. `opacity` is the alpha (dialogs vary it per call).
@@ -1402,6 +1405,7 @@ inline bool BeginOverlayDialog(const OverlayDialogSpec& spec)
// first open of a given dialog this session briefly anchors near the top before it re-centers).
float cardX = vp_pos.x + (vp_size.x - cardWidth) * 0.5f;
float cardY, cardBottomY;
bool hideForMeasure = false; // true on an auto-height dialog's first (unmeasured) frame
const bool fixedHeight = (spec.cardHeight > 0.0f);
if (fixedHeight) {
float cardH = std::min(spec.cardHeight * dp, vp_size.y - 32.0f);
@@ -1419,23 +1423,27 @@ inline bool BeginOverlayDialog(const OverlayDialogSpec& spec)
: vp_pos.y + 16.0f;
cardBottomY = cardY + measuredH;
} else {
// First frame: content height not yet known. Anchor near the top; it re-centers next frame.
// First frame: content height not yet known. Lay it out (so the auto-height child gets
// measured) but keep the card hidden (hideForMeasure below) so it never flashes off-center
// — it appears, already centered, on the next frame once the height is known.
hideForMeasure = true;
cardY = vp_pos.y + vp_size.y * 0.15f;
cardBottomY = vp_pos.y + vp_size.y * spec.cardBottomViewportRatio;
}
}
ImVec2 cardMin(cardX, cardY), cardMax(cardX + cardWidth, cardBottomY);
// Card background — a glass panel unless the content floats directly on the backdrop.
if (!floating) {
// Card background — a glass panel unless the content floats directly on the backdrop. Skipped on
// the measuring frame (its geometry is a placeholder; the whole card is hidden until centered).
if (!floating && !hideForMeasure) {
GlassPanelSpec cardGlass;
cardGlass.rounding = 16.0f; cardGlass.fillAlpha = 35; cardGlass.borderAlpha = 50; cardGlass.borderWidth = 1.0f;
DrawGlassPanel(dl, cardMin, cardMax, cardGlass);
}
// Click outside the card dismisses — but not on the appearing frame (the opening click) or while
// a popup opened from the dialog is showing (a click in an overhanging combo would look outside).
if (spec.p_open && !overlayPopupOpen && !ImGui::IsWindowAppearing() &&
// Click outside the card dismisses — but not on the measuring frame (placeholder geometry), the
// appearing frame (the opening click), or while a popup opened from the dialog is showing.
if (spec.p_open && !hideForMeasure && !overlayPopupOpen && !ImGui::IsWindowAppearing() &&
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
!ImGui::IsMouseHoveringRect(cardMin, cardMax, false)) {
*spec.p_open = false;
@@ -1458,6 +1466,14 @@ inline bool BeginOverlayDialog(const OverlayDialogSpec& spec)
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.5f, 0.5f));
}
// Measuring frame: clip the card to nothing. Content (title + the caller's body) still lays out,
// so the child's auto-height is measured for next frame, but nothing is drawn — no off-center
// flash. EndOverlayDialog pops this before EndChild. Only entered when childVisible (End runs).
if (childVisible && hideForMeasure) {
ImGui::PushClipRect(ImVec2(0, 0), ImVec2(0, 0), false);
g_overlayHideFrameClip = true;
}
if (childVisible && spec.title) {
if (plainHd) {
ImGui::PushFont(Type().h6());
@@ -1491,6 +1507,7 @@ inline bool BeginOverlayDialog(const char* title, bool* p_open, float cardWidth
inline void EndOverlayDialog()
{
if (g_overlayHideFrameClip) { ImGui::PopClipRect(); g_overlayHideFrameClip = false; }
ImGui::EndChild();
// Remember the rendered card height (the child is the last item) so the next frame's
// BeginOverlayDialog can size the glass to the content — kills the empty band under short dialogs.