Element

The table you pass to overlay.add. Every element has an id, a visibility mode, and exactly one visual form: a button, a paint callback, or a panel of widgets.

fieldmodestring = "menu"

Controls when the element is visible:

ModeOverlay openOverlay closed
"menu"✓ shown— hidden
"pinned"✓ shown✓ shown
"hud"— hidden✓ shown
fieldlockModeboolean = false

When true, the element's mode is forced and overlay.setUserMode is ignored for it. When false, the user may override the mode.

fieldhiddenboolean

Reset to false each frame, then read after the OverlayElement hook — set it there to hide the element for that frame.

fieldpassThroughboolean = false

Mouse behaviour of the element's rectangle. false (default): the element consumes mouse events over its bounds — it receives the callbacks below, and elements beneath it get nothing. true: the element (including its widgets) ignores the mouse entirely and events fall through to the next element under the cursor. Elements are tested topmost-first (last drawn = on top), so the highest non-passThrough element under the cursor is always the one that receives events.

fieldmovableboolean

Whether the user can drag the element around by its background. A press only starts a drag if it missed every child widget and then travelled a few pixels, so buttons, textboxes and plain clicks all work normally. Menu-column buttons are never movable — the engine owns that column's layout.

The default depends on who draws the element. A panel (one with children) is drawn by the engine at its own x, y, so dragging it moves what you see: movable defaults to true, and false nails it down. A paint element draws wherever its own callback decides, so a drag would move its clickable area and nothing else — it defaults to false. Set movable = true to opt in, and draw from the self your paint is handed rather than from a position you compute, or the two will part company.

The drag is stored as an offset from whatever position your code asks for, and applied after the OverlayElement hook: a panel that recentres itself every frame stays centred-plus-the-user's-offset instead of snapping back, and one that sets x, y only once stays put. Either way the engine writes the final on-screen position back into x, y, so you can read it (and save it) after the fact.

fieldresizableboolean = false

Whether the user can resize the element by dragging its edges or corners (an 8 px grab zone just inside the border; the mouse cursor changes to show it). Off by default, because most panels are laid out for the size their mod chose. An edge grab beats a child widget that reaches into that zone, and dragging the left or top edge moves the panel as well as sizing it, so the opposite edge stays put. The new size is written into w, h; children are yours to re-place — see OnResize.

fieldminW, minHnumber = 120, 80

Smallest size a user resize may reach. Only consulted when resizable is true. The maximum is the render size.

fieldhoveredboolean

Maintained by the engine: true while this element is the current mouse-hover target, false otherwise. Read-only in spirit — useful inside paint to restyle on hover without writing callbacks.

fieldOnHoverEnterfunction(self)

Overridable callback (GMod-panel style; nil = no-op): called once when the mouse moves onto this element.

Callback argTypeNotes
selfElement (table)The element itself.
fieldOnHoverExitfunction(self)

Overridable callback: called once when the mouse leaves this element (or another element takes the hover).

Callback argTypeNotes
selfElement (table)The element itself.
fieldOnMouseDownfunction(self, x, y, button)

Overridable callback: a mouse button was pressed on this element (and no child widget took the click — widgets inside a panel get first crack, left button only).

Callback argTypeNotes
selfElement (table)The element itself.
x, ynumberPress position in pixels, relative to the element's top-left corner.
buttonnumber1 = left, 2 = middle, 3 = right.
fieldOnWheelfunction(self, x, y, dy)

Overridable callback: the mouse wheel turned over this element — how a long list scrolls. Delivered topmost-first to the first element under the pointer that defines OnWheel, so a plain panel drawn on top cannot swallow the scroll of one underneath it. Unlike clicks this fires with the overlay closed as well, because a pinned list is on screen during play. dy is positive when the wheel goes up (away from you), matching "scroll towards the top of the list".

Callback argTypeNotes
selfElement (table)The element itself.
x, ynumberPointer position in pixels, relative to the element's top-left corner.
dynumberWheel movement; > 0 is up/away, < 0 is down/towards you.
local top = 1
overlay.add("mymod.list", {
    mode = "pinned",
    OnWheel = function(self, x, y, dy)
        top = math.max(1, top - (dy > 0 and 3 or -3))
    end,
    paint = function(d) --[[ draw rows starting at `top` ]] end,
})
fieldOnMouseUpfunction(self, x, y, button)

Overridable callback: the button was released. Sent to the element that received the OnMouseDown (capture semantics — a drag that ends outside the element still notifies it).

Callback argTypeNotes
selfElement (table)The element itself.
x, ynumberRelease position in pixels, relative to the element's top-left corner.
buttonnumber1 = left, 2 = middle, 3 = right.
Bounds required: mouse events use the element's x, y, w, h. Panels always have them; a paint element only takes part if you give it explicit bounds. Pinned / hud elements receive mouse events even while the overlay is closed — and keyboard input too, once one of their textboxes has been clicked into.
local pnl = overlay.add("mymod.box", {
    mode = "pinned", x = 40, y = 40, w = 180, h = 90,
    children = { { type = "label", x = 10, y = 10, text = "drag me" } },
})
function pnl.OnMouseDown(self, x, y, button) log("down at " .. x .. "," .. y) end
function pnl.OnMouseUp(self, x, y, button)   log("released")                  end
function pnl.OnHoverEnter(self) self.style.border = {120,200,255,255} end
function pnl.OnHoverExit(self)  self.style.border = {200,200,215,255} end
fieldOnResizefunction(self, w, h)

Overridable callback: the user is resizing this element (resizable = true). Fires live, on every step of the drag, after the new size has been written into self.w, self.h — re-place your children here so the panel looks right while it is being pulled.

Callback argTypeNotes
selfElement (table)The element itself.
w, hnumberThe new size, already clamped to minW, minH and the render size.
fieldOnDragEndfunction(self)

Overridable callback: a move or a resize finished. Fires once, on release, and only if the element actually moved or changed size — a click that merely landed on the background does not count. This is the place to persist geometry: read self.x, self.y, self.w, self.h and write them out, rather than saving on every step of the drag.

Callback argTypeNotes
selfElement (table)The element itself.
local note = overlay.add("mymod.note", {
    mode = "pinned", x = 24, y = 56, w = 260, h = 178,
    resizable = true, minW = 150, minH = 96,   -- movable is already on
    children = {
        { type = "button",  x = 230, y = 6,  w = 22,  h = 20,  text = "x" },
        { type = "textbox", x = 10,  y = 32, w = 240, h = 136, multiline = true },
    },
})
function note.OnResize(self, w, h)
    self.children[1].x = w - 30            -- button stays on the right edge
    self.children[2].w, self.children[2].h = w - 20, h - 42
end
function note.OnDragEnd(self)
    data.write("note.txt", ("%d %d %d %d"):format(self.x, self.y, self.w, self.h))
end
fieldbuttontable { label, onClick }

Makes the element an auto-laid-out button in the centered overlay menu column. Button elements are ordinary elements underneath: they inherit passThrough, hovered, and the OnHoverEnter / OnHoverExit / OnMouseDown / OnMouseUp callbacks, and the engine writes the computed x, y, w, h back onto the element each frame so you can read where the button ended up.

FieldTypeNotes
labelstringButton text.
onClickfunctionCalled (no arguments) on left-click, after the element's own OnMouseDown.
fieldpaintfunction(d, self)

Makes the element a free-form drawing. Called every frame it's visible with a DrawContext d and the element itself. Paint elements have no built-in input handling.

self.x, self.y, self.w, self.h are final for this frame here — the "OverlayElement" hook has run and any drag the user is in the middle of is already in them. That makes this the one place a movable paint element can draw where it actually is; ignore self and draw from your own numbers instead, and the drag will move the element's clickable area out from under the picture.

Callback argTypeNotes
dDrawContextThe drawing surface.
selfElement (table)The element itself, with this frame's geometry.
fieldchildrenWidget[]

Makes the element a panel: a styled box at x, y, w, h containing native Widgets (positioned relative to the panel). Requires x, y, w, h; accepts a style.

FieldTypeNotes
x, y, w, hnumberPanel position & size, in screen pixels.
styletable?{ bg = {r,g,b,a?}, border = {r,g,b,a?} }
childrenWidget[]Array of Widgets.
One page of the MCCP modding reference. Every section is its own document; search covers all of them.