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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user