Skip to content

Input and focus

When several panels overlap on screen, only one of them owns the mouse and keyboard for that frame. This page explains how Interlude UI resolves that owner each frame, what ConsumeInput and PanelConfig.AcceptsInput do, and how mouse hit-testing works.

This page is the canonical reference for focus resolution. The Panels and modals guide introduces the priority order briefly; the full model lives here.

What focus resolution does

Several panels can be visible at once, and many can overlap the mouse in the same frame. Input-focus resolution picks the single panel that owns the mouse and keyboard for that frame, so a click reaches one panel and the panels beneath it stay inert.

The priority order

BeginFrame runs three steps:

  1. Blocking panels (panels with BlocksInput = true, set by ShowModal) are sorted to the end of the panel list. This makes them "topmost" for the walk that follows.
  2. The frame walks the panel list once and remembers the last panel that matched.
  3. A panel matches if BlocksInput is true, or if AcceptsInput is true and its rect contains the mouse.

The last match wins. Nothing earlier in the list sees input that frame.

This is why the built-in debugger overlay (TICK_DEBUGGER) stays reachable even when a modal is open: the debugger panel is itself shown via ShowModal, so any later modal still slots in after it in the blocking-panel suffix and gives input back when it closes.

How mouse hit-tests resolve

BeginFrame reads m_input.GetMousePosition() and inverts the Y axis to match layout space. The flipped point feeds the priority walk above. For each candidate panel, the test is LayoutToScreen(window.Rect).Contains(mousePos): convert the panel rect from layout space to screen space and check containment.

The Y-flip is the same one applied throughout the input path. For the broader coordinate-space model, refer to Coordinate spaces.

Clip-stack intersection

Every pointer hit-test intersects the widget's screen rect against the active clip rect before checking pointer position. This applies to both hover detection (IsHovered) and click detection (Button). If the intersection is empty (the widget is entirely outside the clip rect), the widget can't be hovered or clicked, regardless of where the pointer is.

This means geometry clipped from view is also clipped from hit-testing. The most common case is a button inside a scroll view that has been scrolled out of the visible port: the button is not renderable at the pointer's position, so it's also not pressable there. The same rule applies to any PushClipRect scope: content pushed past the clip boundary becomes inert to input.

For details on the clip stack itself, refer to Stacks. For the scroll-view patterns that rely on this behaviour, refer to Add a scroll view.

What focus means in practice

The panel selected by the priority walk becomes the hovered panel for the frame. Widgets inside the hovered panel receive IsHovered, click, and keyboard events. Widgets outside it still render, but their Button and Textbox calls do not respond to input that frame.

Focus is resolved once per frame, in BeginFrame. After that, it is constant for the rest of the frame. Calls into your DoUI body read the same hovered panel from start to end.

ConsumeInput

ui.ConsumeInput() claims the current frame's input from inside a panel body. It records that input was consumed; EndFrame carries the flag into the next frame and then clears it, so the consumed state is visible for exactly one frame.

For full-screen modal dialogs, you generally don't need it: a panel shown via ShowModal already blocks input to anything underneath. Reach for ConsumeInput when you have a non-blocking panel that still needs to swallow clicks inside some sub-rect (for example, a transparent HUD overlay where clicks within a button shouldn't fall through to the scene beneath).

Exclude a panel from input

A PanelConfig with AcceptsInput = false (the shape that PanelConfig.OverlayDefault gives you) makes a panel that draws but never claims input, regardless of whether it overlaps the mouse. Read-only HUDs that render on top of the game but should not intercept clicks use this.

The panel is skipped during the matching step of the priority walk above. It still renders normally; it simply never wins focus.

Popups (panels with AutoCloseOnOutsideClick = true, the shape that BeginPopup gives you) get a separate behaviour: BeginFrame watches for mouse-button-down events and walks the open popups. Any popup whose rect does not contain the mouse position hides itself automatically.

To combine outside-click dismiss with input-blocking, set the config explicitly and show via ShowModal: new PanelConfig { AutoCloseOnOutsideClick = true, BackdropColor = ... }, then ui.ShowModal(id).

Pitfalls

Two modals open at once. : The last blocking panel in the panel list owns input. Closing it transfers input to whichever blocking panel is now last. If you need multiple modal layers, be deliberate about open order.

The modal has no dim background. : PanelConfig.BackdropColor is missing or null. Set it to a translucent colour; the backdrop draws automatically before the panel body runs.

A normal panel never receives input. : A modal panel is open, even if it is not visually obvious. Modal panels with no on-screen body (an empty BeginPanel block, for example) still block input below them. Hide the modal when it is not in use.

A panel intercepts input you didn't want it to. : You showed it via ShowModal. Modal panels block everything underneath. Switch to ShowPanel to keep the panel visible without blocking, or close it before handing input back.

Additional resources

  • Panels and modals: the broader panel context, including the visibility state machine.
  • Frame lifecycle: where focus resolution sits in the per-frame flow.
  • Stacks: the panel stack, and the per-panel BlocksInput flag that drives modal behaviour.
  • Show a modal: the procedure that applies the patterns described here.
  • Navigation: a separate focus model that routes directional input, confirm, and cancel inside the hovered panel.