MingaEditor.Layout.FooterOverlays (Minga v0.1.0)

Copy Markdown View Source

BEAM visibility source for the eight footer-band secondary overlays (#2281).

These surfaces (float popup, agent context, tool manager, extension panel, observatory, edit timeline, notifications, extension overlay) historically had no BEAM rect: the Go compositor footer-appended one of them, sizing it frontend-side. The owner ruled them mouse-driven (#2330), so the BEAM now owns their footer-band geometry. This module is the single place that decides which of the eight is visible this frame and how tall its band is, reading the SAME underlying state the render-model builders read (so visibility never drifts from what the frontend actually renders).

visible/1 returns the visible footer overlays as {surface_id, content_height} pairs. MingaEditor.FocusTree turns each into an overlay node via MingaEditor.Layout.OverlayBand.rect/2; MingaEditor.Layout.SurfaceRegistry then places them with the historical stacking z. Only one overlay shows at a time in the fixed bottom band (single-active model, #2268 AC-4), and the band rect is bottom-anchored: its bottom edge sits directly above the minibuffer, exactly where the Go compositor used to footer-append the overlay.

Content height and the residual phantom zone

The rect must CONTAIN the rendered overlay (so a click over it is hit-tested and swallowed) but it should not span the whole band for a short overlay, or the rows below the content phantom-swallow clicks and the overlay renders well above the screen bottom. Two buckets:

  • Count-derivable surfaces (notifications, observatory, edit_timeline, extension_overlay): the BEAM knows the line count the Go renderer draws, so content_height is that count (clamped by the band ceiling, see the per-surface helpers below). The rect is sized to the content and the overlay hugs the screen bottom with no residual phantom zone (at most ~1 row of conservative slack: e.g. a notification item with no body draws 1 line where the count budgets 2).

  • Wrap-dependent surfaces (float_popup, extension_panel, and the dormant agent_context / tool_manager): these wrap text frontend-side where the BEAM only knows an item count, not a wrapped line count, so content_height stays :max (the clamp ceiling). Go bottom-aligns the rendered content within the band (Y = rect.Row + bandHeight - contentLines), so the overlay still hugs the screen bottom; the residual phantom zone (clamp ceiling minus rendered lines) sits ABOVE the content, between the buffer and the overlay, not below it. Per surface, the phantom band is at most band_height - rendered_lines rows tall: float_popup renders 1 + min(line_count, ceiling-1) lines so its phantom zone is the remaining ceiling rows above; extension_panel renders a title plus up to two blocks per visible panel until it fills the band, so its phantom zone shrinks as panels are added; agent_context and tool_manager are dormant (never visible today) so their phantom zone is moot until a future child gives them a render-model source.

Live vs dormant sources (honesty, #2281)

Six surfaces have a live BEAM content source in the render-model path and so can actually become visible here: float popup, extension panel, observatory, edit timeline, notifications, extension overlay. Two are dormant on that path: the agent-context builder is a permanent visible: false stub (MingaEditor.RenderModel.UI.AgentContextBuilder, fed by gui_payload/1 which the traditional shell always returns nil for), and the tool manager is not in the render-model UI at all (it ships only via the legacy protocol/gui.ex path). Their predicates here are wired to the same future sources, so they are placement-ready and will light up automatically when an epic child gives them a BEAM render-model source. Today their predicates never fire, which is correct: an overlay the BEAM never renders must not claim a footer band.

Summary

Types

A visible footer overlay: its registry surface id and its band content height.

Functions

Returns the visible footer-band overlays for the frame, back-to-front by z.

Types

entry()

@type entry() :: {surface_id :: atom(), content_height :: non_neg_integer() | :max}

A visible footer overlay: its registry surface id and its band content height.

Functions

visible(state)

@spec visible(map()) :: [entry()]

Returns the visible footer-band overlays for the frame, back-to-front by z.

Each entry is {surface_id, content_height} where surface_id is a MingaEditor.Layout.SurfaceRegistry surface id and content_height is the BEAM-known content line count for the count-derivable surfaces (clamped by the band ceiling) or :max for the wrap-dependent ones (see the moduledoc).

Order is ascending z (back-to-front): extension overlay (lowest z, painted first) up to float popup (highest z, painted last). This ordering matters for hit-testing: FocusTree.add_footer_band_overlays/3 appends each entry as a child in list order, and FocusTree's hit path reverses children so the LAST-appended (highest z) node wins among same-rect siblings. Returning ascending z here makes the BEAM hit-winner the highest-z node, the SAME surface Go composites on top (its highest-z render winner). If this returned descending z, the lowest-z node would win the reversed hit-test, inverting it against the render winner and breaking the per-surface handlers #2330 builds on top.