Window size constraints
ImGui’s SetNextWindowSizeConstraints clamps the next window between a
min and max corner size. The 2-arg form is enough for a static box constraint;
the 3-arg form takes an ImGuiSizeCallback — a C function pointer ImGui
invokes on every resize so the callback can re-shape the requested size
(aspect-ratio lock, fixed-step quantization, “always square”, etc.).
The boost module imgui/imgui_window_constraints_builtin ships a daslang
wrapper around the 3-arg form: an ImGuiSizeConstraints struct that wraps
a daslang lambda + the existing C++ trampoline (das_invoke_lambda<void>
at src/dasIMGUI.main.cpp:361). End-user code passes the wrapper directly:
let aspect = @ capture(= ratio) (var data : ImGuiSizeCallbackData) : void {
data.DesiredSize = float2(data.DesiredSize.x,
data.DesiredSize.x / ratio)
}
SetNextWindowSizeConstraints(float2(0,0), float2(FLT_MAX, FLT_MAX),
ImGuiSizeConstraints(aspect))
The 2-arg SetNextWindowSizeConstraints(min, max) form stays on the
boost-surface allow-list — only require this module when you need the
callback form.
Source: examples/tutorial/window_size_constraints.das.
Walkthrough
The recording drags each window by its bottom-right corner - the real “drag border” gesture - to a shape that violates its constraint, and the callback snaps it back: the first window, dragged to a wide-short rectangle, springs to a 400x400 square; the second, dragged tall, collapses to 480x270 (16:9); the third, dragged off-grid, lands on 350x200 (nearest 50). Each snapped size is asserted - a drag that didn’t resize, or a callback that didn’t reshape the request, aborts the recording.
1options gen2
2
3require imgui
4require imgui_app
5require opengl/opengl_boost
6require live/glfw_live
7require live/live_api
8require live/live_commands
9require live/live_vars
10require live_host
11require imgui/imgui_live
12require imgui/imgui_boost_runtime
13require imgui/imgui_boost_v2
14require imgui/imgui_widgets_builtin
15require imgui/imgui_containers_builtin
16require imgui/imgui_window_constraints_builtin
17require imgui/imgui_visual_aids
18require math
19
20// =============================================================================
21// TUTORIAL: window_size_constraints — lambda-callback overload for
22// ImGui::SetNextWindowSizeConstraints.
23//
24// The 2-arg form `SetNextWindowSizeConstraints(min, max)` is plain-allowed
25// at user-call sites — clamp the window between two corner sizes. The 3-arg
26// form takes a `ImGuiSizeCallback`, a C function pointer ImGui invokes on
27// every resize so the callback can re-shape the requested size (aspect-ratio
28// lock, fixed step quantization, "always square", ...).
29//
30// The boost module `imgui/imgui_window_constraints_builtin` provides a
31// daslang-side `ImGuiSizeConstraints` struct + ctor that wraps a daslang
32// lambda; the matching 3-arg overload threads the wrapper through the
33// existing C++ trampoline at `src/dasIMGUI.main.cpp:361-384`. End-user code
34// can then write:
35//
36// let aspect = @ capture(= ratio) (var data : ImGuiSizeCallbackData) : void {
37// data.DesiredSize.y = data.DesiredSize.x / ratio
38// }
39// SetNextWindowSizeConstraints(float2(0,0), float2(FLT_MAX, FLT_MAX),
40// ImGuiSizeConstraints(aspect))
41//
42// This tutorial mounts three resizable windows side-by-side — square,
43// aspect-16:9, fixed-step-50 — each with a body line printing its current
44// (width, height) so resizing visibly snaps to the constraint.
45//
46// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/window_size_constraints.das
47// LIVE: daslang-live modules/dasImgui/examples/tutorial/window_size_constraints.das
48// =============================================================================
49
50// Module-scope holders for the three ImGuiSizeConstraints. ImGui stores the
51// callback pointer + user-data for use by the NEXT Begin(); the wrapper
52// struct's address must outlive the SetNextWindowSizeConstraints call.
53// Stack-locals don't survive long enough — auto-fit invokes the callback
54// from inside Begin AFTER the wrapper function has returned. Module
55// scope keeps the struct alive for the lifetime of the program.
56var private SQUARE_CN : ImGuiSizeConstraints
57var private ASPECT_CN : ImGuiSizeConstraints
58var private STEP_CN : ImGuiSizeConstraints
59
60[export]
61def init() {
62 live_create_window("dasImgui window_size_constraints tutorial", 980, 560)
63 live_imgui_init(live_window)
64 // This tutorial relies on FirstUseEver window sizes; disable imgui.ini so the
65 // documented layout (and the recording) is deterministic every run instead of
66 // restoring whatever a prior session left. Tutorial-scoped on purpose — not a
67 // blanket change to live_imgui_init.
68 DisableIniPersistence()
69 let io & = unsafe(GetIO())
70 GetStyle().FontScaleMain = 1.2
71
72 // Seed the three constraints once at init — daslang lambdas hold their
73 // own capture state, so a single per-shape struct is enough for all
74 // future Begin() invocations of the matching window.
75 SQUARE_CN <- ImGuiSizeConstraints(@(var data : ImGuiSizeCallbackData) : void {
76 let s = max(data.DesiredSize.x, data.DesiredSize.y)
77 data.DesiredSize = float2(s, s)
78 })
79 let ratio = 16.0f / 9.0f
80 ASPECT_CN <- ImGuiSizeConstraints(@ capture(= ratio) (var data : ImGuiSizeCallbackData) : void {
81 data.DesiredSize = float2(data.DesiredSize.x,
82 float(int(data.DesiredSize.x / ratio)))
83 })
84 let step = 50.0f
85 STEP_CN <- ImGuiSizeConstraints(@ capture(= step) (var data : ImGuiSizeCallbackData) : void {
86 let rx = float(int(data.DesiredSize.x / step + 0.5f)) * step
87 let ry = float(int(data.DesiredSize.y / step + 0.5f)) * step
88 data.DesiredSize = float2(rx, ry)
89 })
90}
91
92[export]
93def update() {
94 if (!live_begin_frame()) return
95 begin_frame()
96
97 ImGui_ImplGlfw_NewFrame()
98 apply_synth_io_override()
99 NewFrame()
100
101 // -------- 1. Square — max(w, h) wins both dims. --------
102 SetNextWindowPos(ImVec2(20.0f, 60.0f), ImGuiCond.FirstUseEver)
103 SetNextWindowSize(ImVec2(240.0f, 240.0f), ImGuiCond.FirstUseEver)
104 SetNextWindowSizeConstraints(float2(120.0f, 120.0f), float2(FLT_MAX, FLT_MAX),
105 SQUARE_CN)
106 window(SQUARE_WIN, (text = "Square", closable = false,
107 flags = ImGuiWindowFlags.None)) {
108 let s = SQUARE_WIN.size
109 text(SQUARE_TEXT, (text = "Drag border - always square.\n"
110 + "Size: {int(s.x)} x {int(s.y)}"))
111 }
112
113 // -------- 2. Aspect ratio 16:9 — height = width / aspect. --------
114 SetNextWindowPos(ImVec2(290.0f, 60.0f), ImGuiCond.FirstUseEver)
115 SetNextWindowSize(ImVec2(320.0f, 180.0f), ImGuiCond.FirstUseEver)
116 SetNextWindowSizeConstraints(float2(160.0f, 0.0f), float2(FLT_MAX, FLT_MAX),
117 ASPECT_CN)
118 window(ASPECT_WIN, (text = "Aspect 16:9", closable = false,
119 flags = ImGuiWindowFlags.None)) {
120 let s = ASPECT_WIN.size
121 text(ASPECT_TEXT, (text = "Drag border - height tracks width / 16:9.\n"
122 + "Size: {int(s.x)} x {int(s.y)}"))
123 }
124
125 // -------- 3. Fixed step 50 — both dims snap to nearest 50 px. --------
126 SetNextWindowPos(ImVec2(640.0f, 60.0f), ImGuiCond.FirstUseEver)
127 SetNextWindowSize(ImVec2(300.0f, 250.0f), ImGuiCond.FirstUseEver)
128 SetNextWindowSizeConstraints(float2(100.0f, 100.0f), float2(FLT_MAX, FLT_MAX),
129 STEP_CN)
130 window(STEP_WIN, (text = "Step 50", closable = false,
131 flags = ImGuiWindowFlags.None)) {
132 let s = STEP_WIN.size
133 text(STEP_TEXT, (text = "Drag border - both dims snap to 50 px.\n"
134 + "Size: {int(s.x)} x {int(s.y)}"))
135 }
136
137 end_of_frame()
138 Render()
139 var w, h : int
140 live_get_framebuffer_size(w, h)
141 glViewport(0, 0, w, h)
142 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
143 glClear(GL_COLOR_BUFFER_BIT)
144 live_imgui_render()
145
146 live_end_frame()
147}
148
149[export]
150def shutdown() {
151 live_imgui_shutdown()
152 live_destroy_window()
153}
154
155[export]
156def main() {
157 init()
158 while (!exit_requested()) {
159 update()
160 }
161 shutdown()
162}
Requires
One extra module on top of the baseline boost layer:
imgui/imgui_window_constraints_builtin— theImGuiSizeConstraintsstruct + ctor + 3-arg overload ofSetNextWindowSizeConstraints.
The three callback patterns
The tutorial mounts three resizable windows demonstrating the canonical callback shapes:
Square — max(w, h) wins both dims:
let sq <- @ (var data : ImGuiSizeCallbackData) : void {
let s = max(data.DesiredSize.x, data.DesiredSize.y)
data.DesiredSize = float2(s, s)
}
Aspect 16:9 — height tracks width / aspect_ratio:
let ratio = 16.0f / 9.0f
let aspect <- @ capture(= ratio) (var data : ImGuiSizeCallbackData) : void {
data.DesiredSize = float2(data.DesiredSize.x,
data.DesiredSize.x / ratio)
}
Fixed step — both dims snap to the nearest multiple:
let step = 50.0f
let snap <- @ capture(= step) (var data : ImGuiSizeCallbackData) : void {
let rx = float(int(data.DesiredSize.x / step + 0.5f)) * step
let ry = float(int(data.DesiredSize.y / step + 0.5f)) * step
data.DesiredSize = float2(rx, ry)
}
Capture rules follow the standard daslang lambda surface — capture(= var)
for by-value, capture(& var) for by-reference. The @ (no-capture)
form works for callbacks that read only data.
Struct layout
The ImGuiSizeConstraints daslang struct mirrors
das::DasImGuiSizeConstraints in src/dasIMGUI.main.cpp field-by-field:
struct ImGuiSizeConstraints {
context : Context?
callback : lambda<(var data : ImGuiSizeCallbackData) : void>
at : LineInfo?
}
The C++ trampoline reads field offsets directly to dispatch the lambda
through das_invoke_lambda<void>::invoke<ImGuiSizeCallbackData*>. Don’t
re-order the fields — the binding is not symbolic.
Standalone vs live
Same convention as previous tutorials. Resize any of the three windows by dragging a border or corner — the callback fires on every drag delta and quantizes the result.
See also
Full source: examples/tutorial/window_size_constraints.das
app_small ShowExampleAppConstrainedResize (mirrors ImGui’s
imgui_demo.cpp:7885-7978) exercises 9 constraint options across both
overload forms: examples/imgui_demo/app_small.das.
Integration test: tests/integration/test_app_small_constrained_resize.das.
Boost macros — the macro layer.