8.18.34. 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: modules/dasImgui/examples/tutorial/window_size_constraints.das.

8.18.34.1. 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
  2options _comment_hygiene = true
  3options gc
  4
  5require imgui
  6require imgui_app
  7require opengl/opengl_boost
  8require live/glfw_live
  9require live/live_api
 10require live/live_commands
 11require live/live_vars
 12require live_host
 13require imgui/imgui_live
 14require imgui/imgui_boost_runtime
 15require imgui/imgui_boost_v2
 16require imgui/imgui_widgets_builtin
 17require imgui/imgui_containers_builtin
 18require imgui/imgui_window_constraints_builtin
 19require imgui/imgui_visual_aids
 20require math
 21
 22// =============================================================================
 23// TUTORIAL: window_size_constraints — lambda-callback overload for
 24//           ImGui::SetNextWindowSizeConstraints.
 25//
 26// The 2-arg form `SetNextWindowSizeConstraints(min, max)` is plain-allowed
 27// at user-call sites — clamp the window between two corner sizes. The 3-arg
 28// form takes a `ImGuiSizeCallback`, a C function pointer ImGui invokes on
 29// every resize so the callback can re-shape the requested size (aspect-ratio
 30// lock, fixed step quantization, "always square", ...).
 31//
 32// The boost module `imgui/imgui_window_constraints_builtin` provides a
 33// daslang-side `ImGuiSizeConstraints` struct + ctor that wraps a daslang
 34// lambda; the matching 3-arg overload threads the wrapper through the
 35// existing C++ trampoline at `src/dasIMGUI.main.cpp:361-384`. End-user code
 36// can then write:
 37//
 38//   let aspect = @ capture(= ratio) (var data : ImGuiSizeCallbackData) : void {
 39//       data.DesiredSize.y = data.DesiredSize.x / ratio
 40//   }
 41//   SetNextWindowSizeConstraints(float2(0,0), float2(FLT_MAX, FLT_MAX),
 42//                                ImGuiSizeConstraints(aspect))
 43//
 44// This tutorial mounts three resizable windows side-by-side — square,
 45// aspect-16:9, fixed-step-50 — each with a body line printing its current
 46// (width, height) so resizing visibly snaps to the constraint.
 47//
 48// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/window_size_constraints.das
 49// LIVE:       daslang-live modules/dasImgui/examples/tutorial/window_size_constraints.das
 50// =============================================================================
 51
 52// Module-scope holders for the three ImGuiSizeConstraints. ImGui stores the
 53// callback pointer + user-data for use by the NEXT Begin(); the wrapper
 54// struct's address must outlive the SetNextWindowSizeConstraints call.
 55// Stack-locals don't survive long enough — auto-fit invokes the callback
 56// from inside Begin AFTER the wrapper function has returned. Module
 57// scope keeps the struct alive for the lifetime of the program.
 58var private SQUARE_CN : ImGuiSizeConstraints
 59var private ASPECT_CN : ImGuiSizeConstraints
 60var private STEP_CN : ImGuiSizeConstraints
 61
 62[export]
 63def init() {
 64    live_create_window("dasImgui window_size_constraints tutorial", 980, 560)
 65    live_imgui_init(live_window)
 66    // Relies on FirstUseEver window sizes; disable imgui.ini so the documented
 67    // layout (and recording) is deterministic every run instead of restoring a
 68    // prior session. Tutorial-scoped on purpose — not a live_imgui_init change.
 69    DisableIniPersistence()
 70    let io & = unsafe(GetIO())
 71    GetStyle().FontScaleMain = 1.2
 72
 73    // Seed the three constraints once at init — daslang lambdas hold their
 74    // own capture state, so a single per-shape struct is enough for all
 75    // future Begin() invocations of the matching window.
 76    SQUARE_CN <- ImGuiSizeConstraints(@(var data : ImGuiSizeCallbackData) : void {
 77        let s = max(data.DesiredSize.x, data.DesiredSize.y)
 78        data.DesiredSize = float2(s, s)
 79    })
 80    let ratio = 16.0f / 9.0f
 81    ASPECT_CN <- ImGuiSizeConstraints(@ capture(= ratio) (var data : ImGuiSizeCallbackData) : void {
 82        data.DesiredSize = float2(data.DesiredSize.x,
 83                                  float(int(data.DesiredSize.x / ratio)))
 84    })
 85    let step = 50.0f
 86    STEP_CN <- ImGuiSizeConstraints(@ capture(= step) (var data : ImGuiSizeCallbackData) : void {
 87        let rx = float(int(data.DesiredSize.x / step + 0.5f)) * step
 88        let ry = float(int(data.DesiredSize.y / step + 0.5f)) * step
 89        data.DesiredSize = float2(rx, ry)
 90    })
 91}
 92
 93[export]
 94def update() {
 95    if (!live_begin_frame()) return
 96    begin_frame()
 97
 98    ImGui_ImplGlfw_NewFrame()
 99    apply_synth_io_override()
100    NewFrame()
101
102    // -------- 1. Square — max(w, h) wins both dims. --------
103    SetNextWindowPos(ImVec2(20.0f, 60.0f), ImGuiCond.FirstUseEver)
104    SetNextWindowSize(ImVec2(240.0f, 240.0f), ImGuiCond.FirstUseEver)
105    SetNextWindowSizeConstraints(float2(120.0f, 120.0f), float2(FLT_MAX, FLT_MAX),
106                                 SQUARE_CN)
107    window(SQUARE_WIN, (text = "Square", closable = false,
108                        flags = ImGuiWindowFlags.None)) {
109        let s = SQUARE_WIN.size
110        text(SQUARE_TEXT, (text = "Drag border - always square.\n"
111                                + "Size: {int(s.x)} x {int(s.y)}"))
112    }
113
114    // -------- 2. Aspect ratio 16:9 — height = width / aspect. --------
115    SetNextWindowPos(ImVec2(290.0f, 60.0f), ImGuiCond.FirstUseEver)
116    SetNextWindowSize(ImVec2(320.0f, 180.0f), ImGuiCond.FirstUseEver)
117    SetNextWindowSizeConstraints(float2(160.0f, 0.0f), float2(FLT_MAX, FLT_MAX),
118                                 ASPECT_CN)
119    window(ASPECT_WIN, (text = "Aspect 16:9", closable = false,
120                        flags = ImGuiWindowFlags.None)) {
121        let s = ASPECT_WIN.size
122        text(ASPECT_TEXT, (text = "Drag border - height tracks width / 16:9.\n"
123                                + "Size: {int(s.x)} x {int(s.y)}"))
124    }
125
126    // -------- 3. Fixed step 50 — both dims snap to nearest 50 px. --------
127    SetNextWindowPos(ImVec2(640.0f, 60.0f), ImGuiCond.FirstUseEver)
128    SetNextWindowSize(ImVec2(300.0f, 250.0f), ImGuiCond.FirstUseEver)
129    SetNextWindowSizeConstraints(float2(100.0f, 100.0f), float2(FLT_MAX, FLT_MAX),
130                                 STEP_CN)
131    window(STEP_WIN, (text = "Step 50", closable = false,
132                      flags = ImGuiWindowFlags.None)) {
133        let s = STEP_WIN.size
134        text(STEP_TEXT, (text = "Drag border - both dims snap to 50 px.\n"
135                              + "Size: {int(s.x)} x {int(s.y)}"))
136    }
137
138    end_of_frame()
139    Render()
140    var w, h : int
141    live_get_framebuffer_size(w, h)
142    glViewport(0, 0, w, h)
143    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
144    glClear(GL_COLOR_BUFFER_BIT)
145    live_imgui_render()
146
147    live_end_frame()
148}
149
150[export]
151def shutdown() {
152    live_imgui_shutdown()
153    live_destroy_window()
154}
155
156[export]
157def main() {
158    init()
159    while (!exit_requested()) {
160        update()
161        maybe_collect_gc()
162    }
163    shutdown()
164}

8.18.34.1.1. Requires

One extra module on top of the baseline boost layer:

  • imgui/imgui_window_constraints_builtin — the ImGuiSizeConstraints struct + ctor + 3-arg overload of SetNextWindowSizeConstraints.

8.18.34.1.2. The three callback patterns

The tutorial mounts three resizable windows demonstrating the canonical callback shapes:

Squaremax(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.

8.18.34.1.3. 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.

8.18.34.1.4. 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: modules/dasImgui/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: modules/dasImgui/tests/test_app_small_constrained_resize.das.

Boost macros — the macro layer.