Agent skill
forge-draggable-windows
Add draggable, z-ordered, collapsible windows to a ForgeUiContext application. Wraps the existing immediate-mode UI context with deferred draw ordering and input routing by z-order.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/forge-draggable-windows-code-review-benchmar-coderabbit-prs2-forg
SKILL.md
Add draggable windows with z-ordering and collapse/expand to any application
that uses ForgeUiContext. Based on UI Lesson 10.
When to use
- You need draggable floating panels that users can rearrange
- You need z-ordering so overlapping windows draw and receive input correctly
- You need a collapse toggle to let users minimize windows to their title bar
- You want deferred draw ordering (per-window draw lists sorted by z at frame end)
- You need input routing that respects z-order (foreground windows block clicks to background windows in overlap regions)
Key API calls
forge_ui_wctx_init(wctx, ctx)-- initialize a window context wrapping an existingForgeUiContextforge_ui_wctx_free(wctx)-- free per-window draw list buffersforge_ui_wctx_begin(wctx)-- begin a frame (determines hovered window from previous frame data, resets per-frame state)forge_ui_wctx_end(wctx)-- end a frame (sorts draw lists by z_order, appends to main buffers in back-to-front order)forge_ui_wctx_window_begin(wctx, id, title, state)-- begin a window (returns true if expanded, false if collapsed or invalid)forge_ui_wctx_window_end(wctx)-- end a window (computes content height, draws scrollbar, restores main buffers)
Correct order
- Initialize a
ForgeUiWindowContextwrapping yourForgeUiContext - Each frame: call
forge_ui_ctx_begin(), thenforge_ui_wctx_begin() - Declare windows:
window_begin/ widgets /window_endblocks - End frame: call
forge_ui_wctx_end(), thenforge_ui_ctx_end()
Widget IDs must be unique and each window reserves three IDs: id for the
window/drag handle, id+1 for the scrollbar, id+2 for the collapse toggle.
When window_begin returns false (collapsed or invalid), skip all child
widgets and do not call window_end.
Key concepts
- ForgeUiWindowState -- application-owned persistent state:
rect(position/size, updated by drag),scroll_y(content scroll offset),collapsed(toggled by clicking the collapse button),z_order(draw priority, higher = on top, updated on click) - Grab offset -- stored on the press frame as
(mouse - rect.origin); used during drag to maintain the relative click position - Bring-to-front -- on any mouse press inside a window, set
z_order = max(all z) + 1 - Deferred draw -- per-window vertex/index buffers assembled at frame end in back-to-front z-order
- Input routing pre-pass --
wctx_beginscans previous frame's window rects to find the topmost window under the mouse; widgets in other windows silently fail hit tests - Keyboard suppression -- widgets in covered windows preserve visual focus but do not process keyboard input (games can suppress window input for game controls without the window appearing unfocused)
Common mistakes
- Forgetting
wctx_end()-- without it, per-window draw lists are never appended to the main buffer and nothing renders - Calling
window_endwhen collapsed --window_beginreturns false for collapsed windows; callingwindow_endin that case corrupts state - ID collisions -- each window needs 3 contiguous IDs (
id,id+1,id+2); overlapping ranges cause unpredictable hot/active behavior - Not checking
wctx_initreturn -- returns false if the context pointer is null or allocation fails - Resetting grab offset -- the
ForgeUiWindowContextgrab offset must persist across frames (not reset bywctx_begin) because drags span multiple frames - Ignoring one-frame lag --
hovered_window_iduses previous frame data; z-order changes take effect on the next frame's input routing
Ready-to-use template
Three overlapping windows
#include "ui/forge_ui.h"
#include "ui/forge_ui_window.h"
/* Window IDs (each reserves id, id+1, id+2) */
#define ID_WIN_A 100
#define ID_WIN_B 200
#define ID_WIN_C 300
/* Widget IDs inside windows */
#define ID_CB_1 110
#define ID_SLIDER 120
/* Widget dimensions */
#define LABEL_HEIGHT 26.0f
#define CHECKBOX_HEIGHT 28.0f
#define SLIDER_HEIGHT 30.0f
#define SLIDER_MIN 0.0f
#define SLIDER_MAX 100.0f
/* Application-owned window state (persists across frames) */
ForgeUiWindowState win_a = {
.rect = { 30.0f, 30.0f, 260.0f, 280.0f },
.scroll_y = 0.0f,
.collapsed = false,
.z_order = 0
};
ForgeUiWindowState win_b = {
.rect = { 200.0f, 80.0f, 260.0f, 200.0f },
.scroll_y = 0.0f,
.collapsed = false,
.z_order = 1
};
/* Application-owned widget state */
bool option_a = true;
float slider_val = 50.0f;
/* --- Frame loop --- */
ForgeUiContext ctx;
forge_ui_ctx_init(&ctx, &atlas);
ForgeUiWindowContext wctx;
forge_ui_wctx_init(&wctx, &ctx);
/* Each frame: */
forge_ui_ctx_begin(&ctx, mouse_x, mouse_y, mouse_down);
ctx.scroll_delta = scroll_delta;
forge_ui_wctx_begin(&wctx);
/* Window A: settings */
if (forge_ui_wctx_window_begin(&wctx, ID_WIN_A, "Settings", &win_a)) {
(void)forge_ui_ctx_checkbox_layout(
wctx.ctx, ID_CB_1, "Option A", &option_a, CHECKBOX_HEIGHT);
(void)forge_ui_ctx_slider_layout(
wctx.ctx, ID_SLIDER, &slider_val,
SLIDER_MIN, SLIDER_MAX, SLIDER_HEIGHT);
forge_ui_wctx_window_end(&wctx);
}
/* Window B: status */
if (forge_ui_wctx_window_begin(&wctx, ID_WIN_B, "Status", &win_b)) {
forge_ui_ctx_label_layout(wctx.ctx, "Hello, windows!",
LABEL_HEIGHT, 0.9f, 0.9f, 0.9f, 1.0f);
forge_ui_wctx_window_end(&wctx);
}
forge_ui_wctx_end(&wctx); /* sorts and assembles draw lists */
forge_ui_ctx_end(&ctx); /* finalizes hot/active state */
/* Use ctx.vertices, ctx.indices, ctx.vertex_count, ctx.index_count
* for rendering — vertices are in correct z-sorted draw order. */
Key observations
- Pass
wctx.ctx(not&ctx) to widget functions inside a window -- the window context redirects the vertex/index pointers window_beginreturns false for both collapsed windows and validation failures -- never callwindow_endwhen it returns false- Widget return values (
checkbox_layout,slider_layout) indicate whether the value changed, not success/failure --(void)cast is fine when the result is not needed - Set
ctx.scroll_deltabeforewctx_beginso the input routing pre-pass can associate the scroll with the correct window
Reference
- UI Lesson 10 -- Windows -- full walkthrough with diagrams and 12-frame demo
- UI Lesson 09 -- Panels and Scrolling -- the panel infrastructure that windows extend
- UI Lesson 08 -- Layout -- the layout system used for widget positioning inside windows
common/ui/forge_ui_window.h-- window system implementationcommon/ui/forge_ui_ctx.h-- the underlying immediate-mode UI context
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?