Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit61a33237e0

README.md

ui_observe

What the last UI frame painted, which widget owns a point, and why a widget is not on screen.

Reached through the ui namespace — ui.observe, ui.diagnose, ui.hitTest, ui.pointerWidget, ui.paintOrder, ui.invisibilityReasons — and served to a non-Luau reader at /zero/runtime/ui/<screen>/paint.json.

The reading

ui.observe() returns one document. Its widgets rows cover every widget any registered screen's tree holds, each carrying:

fieldwhat it is
parentthe nearest ancestor in its screen's tree carrying an id
rectthe box layout gave the widget
clipthe clip it painted under — every overflow: hidden/clip/scroll box above it, intersected down from the viewport
visiblethe part of rect that survived clip and the viewport; w/h are zero when nothing did
viewportthe region the pass that recorded it paints into: the whole window for a screen on the window, the rect a Scene panel presents the camera's picture in for a screen in that picture
paintIndexorder within the frame, so two identical rects still separate
paintedwhether the pass that recorded it is the one drawing the viewport
opaqueFillwhether it filled its whole box at full alpha
hoveredwhether the pointer was over it on the recorded frame
reasonits verdict, from the closed set below
detaila sentence naming what the verdict is about
occludedBythe widget covering it, when reason is occluded

generation advances once per re-rendered frame. Two calls reporting the same number describe the same frame.

Every rect, the document's own viewport (the window) and its pointer are in logical points on the window, the space ui.getLayoutInfo and gui.bounds answer in, wherever a widget's screen draws. A screen in the camera's picture lays out on the camera's canvas (the size ui.screenSize() reports); while a Scene panel presents that picture, its rects are mapped onto the window through the panel. Times ui.pixelRatio() a point is in the window's physical pixels, where Zin.test's mouse primitives and the inputSim toolbox's click / mouseMove with target "ui" place a pointer.

The verdicts

ui.invisibilityReasons() lists them:

reasonwhat the engine found
paintedit reached the frame
noSuchWidgetno registered screen's element tree carries the id
hiddenByAuthorprops.visible = false on it or an ancestor kept it out of the render
screenNotShownits screen is registered and not shown
neverLaidOutits screen is shown and the frame laid out no box
zeroSizeits own box has no area
offViewportits box sits entirely outside the viewport of the pass that drew it (the row's viewport)
clippedByAncestoran ancestor's clip removed the whole box
occludeda later widget covered it with an opaque fill
screenNotOnViewportits box is fine and the camera holding the viewport excludes its screen's render layer, so the frame measured it without drawing it

The resolution reports the nearest cause among them — a widget both clipped away and covered reports the clip — and painted when nothing kept the widget out.

The camera's render-layer mask decides the screens in the camera's picture. With no Scene panel presenting the game viewport, the window is that picture too, and the mask decides every screen on it. While a Scene panel presents the game viewport, the window around it is the editor's own picture: a screen placed on the window (ui.setScreenSpace, and the default for a screen on EditorUI) paints there whatever the camera's mask holds, and reads painted. A screen placed in the camera's picture reads screenNotOnViewport under a camera that excludes its layer, with or without a Scene panel.

The pointer

ui.hitTest(x, y) names the widget a pointer at that point reaches and the stack beneath it, topmost first, in logical points on the window. Where several boxes take the point in, the one that paints last is the one that gets it, and the containers holding them follow — nearest first, by parent. ui.pointerWidget() reports where the pointer sat, whether the UI claimed it, and which widget the frame reported it over.