Paper organises its UI as a tree of elements. Every frame that tree is built from scratch: you callDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ProwlEngine/Prowl.Paper/llms.txt
Use this file to discover all available pages before exploring further.
Box, Row, or Column, optionally step inside them with Enter(), and Paper assembles the parent-child relationships automatically through an internal stack. This page explains exactly how that stack works, what an ElementHandle is, and the tools available for managing identity in complex scenarios.
The Three Element Primitives
All elements in Paper are created through three methods onPaper:
| Method | What it creates |
|---|---|
paper.Box(id) | A generic container with no default layout direction (inherits Column) |
paper.Row(id) | A container that arranges its children horizontally |
paper.Column(id) | A container that arranges its children vertically |
Row and Column are simply Box with .LayoutType(LayoutType.Row) or .LayoutType(LayoutType.Column) pre-applied. All three return an ElementBuilder you can chain style and event methods on.
Establishing Parent–Child Relationships with Enter()
Calling .Enter() on an ElementBuilder returns an IDisposable that pushes the element onto the internal element stack. Any element created inside the using block is automatically added as a child of the entered element. When the using block ends, the element is popped off the stack.
The Element Stack
Internally, Paper maintains aStack<ElementHandle> called _elementStack. Its top item is always accessible as paper.CurrentParent. When you call paper.Box(...), the new element is immediately added as a child of CurrentParent. When .Enter() is called, that new element is pushed onto the stack so subsequent elements become its children.
The root element is always on the bottom of the stack and is never popped. It covers the full screen area and is the implicit parent of any top-level elements you create.
ElementHandle: a Lightweight Reference
ElementHandle is a readonly struct that identifies a single element. It holds a reference to the owning Paper instance and an integer index into the element array. You can use it to access the element’s layout data after the frame, pass it to storage APIs, or add custom render actions.
ElementHandle.IsValid returns true when the handle refers to a live element in the current frame’s array. Accessing handle.Data on an invalid handle will throw.
Moving Elements to the Root with MoveToRoot()
Popups, tooltips, and modals should render above all other content regardless of where they are declared in code. MoveToRoot() detaches the current element from its existing parent and re-attaches it as a direct child of the root element.
MoveToRoot() operates on CurrentParent at the point it is called — call it immediately after .Enter(), before adding children, so those children are scoped under the re-parented element.Scoping IDs in Loops with PushID / PopID
Element IDs are hashed from the string ID, the call-site line number, and the parent’s ID. When you create elements inside a loop, multiple iterations execute the same line with the same string — producing duplicate hashes and a thrown exception.
The solution is to either pass the loop index as intID:
PushID / PopID to create a new ID scope:
PushID(string) and PushID(int) both combine with the current top of the ID stack, so scopes nest correctly. Always pair every PushID with a PopID.
How the Tree Is Cleaned Up Each Frame
At the start ofBeginFrame, Paper calls ClearElements() and rebuilds the root. The _createdElements hash set tracks every element hash registered during the frame. At the end of EndFrame, EndOfFrameCleanupStorage() iterates over all stored per-element data and removes entries whose ID was not present in _createdElements — meaning the element was not declared this frame and therefore no longer exists.
This automatic cleanup means you never need to explicitly destroy elements. Simply stop declaring them and their storage evaporates on the next frame.