The classic GUI layer remains useful for lightweight battle text, markers, drag handles, and native screen-space widgets that do not need an HTML document.
GUI components and the Python GUI package
import GUI accesses native screen components. The Python gui package owns helpers such as g_guiResetters. These component roots have their own lifetime; creating one does not attach a Scaleform view or a Gameface window. Use the GUI framework guide when you need an application-owned view.
Attach a top-level component with GUI.addRoot, or attach a child through its parent's addChild. Remove through the matching owner. DebugView checks the window's parent before choosing delChild or GUI.delRoot during destruction. Its child components are removed before the window reference is cleared.
Owned overlay pattern
Illustrative: this class fragment shows ownership and teardown, not a complete runnable overlay. Supply its layout, input and callback helpers for your owning view or battle lifecycle. It follows the root and resetter pattern demonstrated by DebugView.
import GUI
from gui import g_guiResetters
class Overlay(object):
def __init__(self):
self._root = None
self._text = None
self._resetter = None
self._drag_callback = None
self._refresh_callback = None
def create(self):
if self._root is not None:
return
self._root = GUI.Window()
self._text = GUI.Text('')
self._root.addChild(self._text)
self._resetter = self._on_screen_reset
GUI.addRoot(self._root)
g_guiResetters.add(self._resetter)
self._restore_and_clamp_position()
def destroy(self):
self._cancel_callbacks()
self._unbind_input()
if self._resetter is not None:
g_guiResetters.discard(self._resetter)
self._resetter = None
if self._root is not None:
self._root.delChild(self._text)
GUI.delRoot(self._root)
self._text = None
self._root = None
Resolution and scaling
- Register a resetter or resolution callback once.
- Recalculate dimensions and clamp positions whenever resolution or scale changes.
- Persist a coordinate system you can convert reliably on the next session.
- Do not read or encode large asset sets on a high-frequency render path.
Pixel modes and coordinate deltas
Size modes, position modes and anchors are separate settings. gui_utils.setPixMode sets all four modes; setting width and height alone does not select pixel positioning.
Source excerpt from setPixMode:
component.heightMode = GUI.Simple.eSizeMode.PIXEL
component.widthMode = GUI.Simple.eSizeMode.PIXEL
component.verticalPositionMode = GUI.Simple.ePositionMode.PIXEL
component.horizontalPositionMode = GUI.Simple.ePositionMode.PIXEL
Keep pixel deltas, clip-space deltas and absolute positions separate. The same helper module's pixToClipVector2 scales a displacement using the screen resolution and flips its Y direction. It adds no origin offset for an absolute screen point; DebugView.LU2BW separately accounts for the parent size and origin.
Device resetter lifetime
onRecreateDevice iterates g_guiResetters directly and invokes each callback without arguments. Keep the handler quick and repeatable, reread current dimensions, and avoid adding or removing resetters during that dispatch. Register once per component lifetime and unregister before releasing its references.
These are version-sensitive Python caller contracts, not a rendering or input-delivery test. Validate visual layout, clipping, input and device recreation in the client before shipping an overlay.
Refresh an existing GUI component
Changing data on disk and changing an onscreen component are separate operations. For a caption, call the ResMgr reader from the same single-file mod and assign the fresh string to your existing GUI.Text component.
Illustrative method fragment for the overlay above; read_overlay_caption is the reader from that linked example. Invoke it through your mod's own reload action while the overlay is alive.
def reload_content(self):
if self._root is None:
return
caption = read_overlay_caption()
self._text.text = caption
The reader completes before the assignment, so an opening or validation failure leaves the prior caption in place. The caller should surface the failure; if it catches an unexpected exception, keep its traceback in the mod's logger. Do not replace the text with an empty fallback just to hide a failed reload.
DebugView demonstrates changes to text, colour, visibility, position, size and textureName on owned components. Those property updates do not require recreating the entire client. If your component tree changes, remove your callbacks, children and root through their matching owners before constructing its replacement.
Requesting a different image path
The example uses a virtual file path. Some framework views instead consume mapped texture IDs or formatted image links; use the image representation guide to keep those contracts separate.
Put a compatible image in your mod's loose resource directory and assign its virtual path to the component you own. For example, a file at res_mods/x.x.x.x/gui/mods/example/panel-v2.dds is addressed as below; replace the version placeholder with the running client version.
# Illustrative: self._root is an existing owned GUI.Window.
self._root.textureName = 'gui/mods/example/panel-v2.dds'
This requests a different resource key, as the client's setTextureName pattern does. It does not prove that overwriting image bytes at an already-loaded path refreshes the renderer, or that a newly added file is immediately visible. ResMgr.purge is not evidence of GUI texture eviction. Check the resource/cache guide when the new file resolves but the visible asset remains stale.
Dragging without jitter
Pseudocode: the helper names below describe your overlay implementation, not client APIs.
Capture the initial cursor and overlay position, then derive each new position from those fixed values.
def begin_drag():
global _drag_start_cursor, _drag_start_position
_drag_start_cursor = current_screen_cursor()
_drag_start_position = overlay_position()
schedule_drag_tick()
def drag_tick():
global _drag_callback
_drag_callback = None
if not dragging or not left_button_is_down():
finish_drag()
return
cursor = current_screen_cursor()
dx = cursor.x - _drag_start_cursor.x
dy = cursor.y - _drag_start_cursor.y
set_overlay_position(clamp(_drag_start_position.x + dx,
_drag_start_position.y + dy))
schedule_drag_tick()
Performance-sensitive visuals
- Warm bounded assets from an owned lifecycle point, not at module import.
- Cap files, total bytes, frame counts, image dimensions, and bridge calls.
- Spread unavoidable main-thread work across bounded callbacks and cancel it on teardown.
- Prefer one validated sprite atlas over many loose frames where the renderer supports it.
- Keep a normal on-demand fallback when early warming is not ready.
Cleanup checklist
- Remove the root and all child components.
- Remove resolution resetters and input handlers.
- Cancel refresh, drag, and asset-warming callbacks.
- Save position at drag completion or teardown.
- Clear references so a later context creates a fresh overlay.