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).
|
// 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::unordered_map<std::string, float> g_overlayCardHeights;
|
||||||
inline std::string g_overlayCurrentKey;
|
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),
|
// 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).
|
// 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).
|
// 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 cardX = vp_pos.x + (vp_size.x - cardWidth) * 0.5f;
|
||||||
float cardY, cardBottomY;
|
float cardY, cardBottomY;
|
||||||
|
bool hideForMeasure = false; // true on an auto-height dialog's first (unmeasured) frame
|
||||||
const bool fixedHeight = (spec.cardHeight > 0.0f);
|
const bool fixedHeight = (spec.cardHeight > 0.0f);
|
||||||
if (fixedHeight) {
|
if (fixedHeight) {
|
||||||
float cardH = std::min(spec.cardHeight * dp, vp_size.y - 32.0f);
|
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;
|
: vp_pos.y + 16.0f;
|
||||||
cardBottomY = cardY + measuredH;
|
cardBottomY = cardY + measuredH;
|
||||||
} else {
|
} 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;
|
cardY = vp_pos.y + vp_size.y * 0.15f;
|
||||||
cardBottomY = vp_pos.y + vp_size.y * spec.cardBottomViewportRatio;
|
cardBottomY = vp_pos.y + vp_size.y * spec.cardBottomViewportRatio;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImVec2 cardMin(cardX, cardY), cardMax(cardX + cardWidth, cardBottomY);
|
ImVec2 cardMin(cardX, cardY), cardMax(cardX + cardWidth, cardBottomY);
|
||||||
|
|
||||||
// Card background — a glass panel unless the content floats directly on the backdrop.
|
// Card background — a glass panel unless the content floats directly on the backdrop. Skipped on
|
||||||
if (!floating) {
|
// the measuring frame (its geometry is a placeholder; the whole card is hidden until centered).
|
||||||
|
if (!floating && !hideForMeasure) {
|
||||||
GlassPanelSpec cardGlass;
|
GlassPanelSpec cardGlass;
|
||||||
cardGlass.rounding = 16.0f; cardGlass.fillAlpha = 35; cardGlass.borderAlpha = 50; cardGlass.borderWidth = 1.0f;
|
cardGlass.rounding = 16.0f; cardGlass.fillAlpha = 35; cardGlass.borderAlpha = 50; cardGlass.borderWidth = 1.0f;
|
||||||
DrawGlassPanel(dl, cardMin, cardMax, cardGlass);
|
DrawGlassPanel(dl, cardMin, cardMax, cardGlass);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click outside the card dismisses — but not on the appearing frame (the opening click) or while
|
// Click outside the card dismisses — but not on the measuring frame (placeholder geometry), the
|
||||||
// a popup opened from the dialog is showing (a click in an overhanging combo would look outside).
|
// appearing frame (the opening click), or while a popup opened from the dialog is showing.
|
||||||
if (spec.p_open && !overlayPopupOpen && !ImGui::IsWindowAppearing() &&
|
if (spec.p_open && !hideForMeasure && !overlayPopupOpen && !ImGui::IsWindowAppearing() &&
|
||||||
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
||||||
!ImGui::IsMouseHoveringRect(cardMin, cardMax, false)) {
|
!ImGui::IsMouseHoveringRect(cardMin, cardMax, false)) {
|
||||||
*spec.p_open = false;
|
*spec.p_open = false;
|
||||||
@@ -1458,6 +1466,14 @@ inline bool BeginOverlayDialog(const OverlayDialogSpec& spec)
|
|||||||
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.5f, 0.5f));
|
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 (childVisible && spec.title) {
|
||||||
if (plainHd) {
|
if (plainHd) {
|
||||||
ImGui::PushFont(Type().h6());
|
ImGui::PushFont(Type().h6());
|
||||||
@@ -1491,6 +1507,7 @@ inline bool BeginOverlayDialog(const char* title, bool* p_open, float cardWidth
|
|||||||
|
|
||||||
inline void EndOverlayDialog()
|
inline void EndOverlayDialog()
|
||||||
{
|
{
|
||||||
|
if (g_overlayHideFrameClip) { ImGui::PopClipRect(); g_overlayHideFrameClip = false; }
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
// Remember the rendered card height (the child is the last item) so the next frame's
|
// 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.
|
// BeginOverlayDialog can size the glass to the content — kills the empty band under short dialogs.
|
||||||
|
|||||||
Reference in New Issue
Block a user