8.19.12. With disabled / font / button_repeat

Four stateless scope-wrappers landed alongside the v2 boost surface. They share the same shape: a top-level function that takes a single value plus a trailing block, pushes the corresponding ImGui scope on entry, pops on exit. None of them emit telemetry — no [widget] / [container] annotation, no idents — they exist to subsume the raw Push* / Pop* pairs that v2’s lint flags as invisible state changes.

  • with_disabled(disabled, blk)BeginDisabled / EndDisabled.

  • with_font(font, size, blk)PushFont(font, size) / PopFont; size defaults to 0.0f (keep the current size), so with_font(f) { ... } is a pure font swap.

  • with_button_repeat(repeat, blk)PushItemFlag(ImGuiItemFlags.ButtonRepeat, repeat) / PopItemFlag (ImGui obsoleted the PushButtonRepeat shorthand in 1.91).

  • with_clip_rect(min, max, isect, blk)PushClipRect / PopClipRect.

checkbox(ENABLED_MASTER, (text = "Enable child group"))
with_disabled(!ENABLED_MASTER.value) {
    button(CHILD_SAVE, (text = "Save"))
    button(CHILD_LOAD, (text = "Load"))
    slider_float(CHILD_VOL, (text = "Volume"))
}

The block’s interior renders normally; the wrapper’s argument drives the ImGui-stack push that surrounds it. Toggling ENABLED_MASTER flips with_disabled’s argument and the child widgets re-render greyed (or active) on the next frame.

Source: modules/dasImgui/examples/tutorial/with_disabled.das.

8.19.12.1. Walkthrough

The recording drives the with_disabled contrast with real synthetic input and self-verifies each step. While the checkbox is off it clicks the greyed Save button — nothing happens. It then ticks the checkbox on (asserting the value flipped), clicks Save again, and asserts CHILD_SAVE.click_count is now exactly one — proving the earlier greyed click never registered. Finally it presses and holds the + button under with_button_repeat and asserts the counter climbs twice under the single hold — a genuine repeat stream, not one click. Any step that failed to land would abort 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_scope_builtin
 19require imgui/imgui_visual_aids
 20
 21// =============================================================================
 22// TUTORIAL: with_disabled — stateless scope-wrapper family.
 23//
 24// Four new wrappers landed alongside the v2 boost surface. They share the
 25// same shape: a top-level function that takes a single value + trailing
 26// block, pushes the corresponding ImGui scope on entry, pops on exit. None
 27// emit telemetry (no [widget] / [container] annotation, no idents).
 28//
 29//   with_disabled(disabled, blk)         — BeginDisabled / EndDisabled
 30//   with_font(font, blk)                 — PushFont / PopFont
 31//   with_button_repeat(repeat, blk)      — PushButtonRepeat / PopButtonRepeat
 32//   with_clip_rect(min, max, isect, blk) — PushClipRect / PopClipRect
 33//
 34// They subsume the raw Push/Pop pairs that v2's lint flags as invisible.
 35//
 36// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/with_disabled.das
 37// LIVE:       daslang-live modules/dasImgui/examples/tutorial/with_disabled.das
 38// =============================================================================
 39
 40[export]
 41def init() {
 42    live_create_window("dasImgui with_disabled family", 760, 520)
 43    live_imgui_init(live_window)
 44    let io & = unsafe(GetIO())
 45    GetStyle().FontScaleMain = 1.4
 46}
 47
 48[export]
 49def update() {
 50    if (!live_begin_frame()) return
 51    begin_frame()
 52
 53    ImGui_ImplGlfw_NewFrame()
 54    apply_synth_io_override()
 55    NewFrame()
 56
 57    SetNextWindowPos(ImVec2(60.0, 60.0), ImGuiCond.Always)
 58    SetNextWindowSize(ImVec2(640.0, 440.0), ImGuiCond.Always)
 59    window(SCOPES_WIN, (text = "Scope wrappers", closable = false,
 60                        flags = ImGuiWindowFlags.None)) {
 61        separator_text("with_disabled")
 62
 63        text("Toggle ENABLED_MASTER; child widgets follow.")
 64        checkbox(ENABLED_MASTER, (text = "Enable child group"))
 65
 66        // The whole block inside `with_disabled(...)` is rendered greyed-out
 67        // when the disabled flag is true; child clicks are inert.
 68        with_disabled(!ENABLED_MASTER.value) {
 69            if (button(CHILD_SAVE, (text = "Save"))) {
 70                print("save\n")
 71            }
 72            same_line(SL_CHILD)
 73            if (button(CHILD_LOAD, (text = "Load"))) {
 74                print("load\n")
 75            }
 76            slider_float(CHILD_VOL, (text = "Volume"))
 77        }
 78        text("CHILD_SAVE: clicked {CHILD_SAVE.click_count} times")
 79
 80        separator_text("with_button_repeat")
 81
 82        // Inside with_button_repeat(true), holding the button fires click
 83        // events repeatedly — useful for steppers.
 84        text("Hold the button - click_count keeps rising.")
 85        with_button_repeat(true) {
 86            if (button(STEP_UP, (text = "+"))) {
 87                print("step up: {STEP_UP.click_count}\n")
 88            }
 89        }
 90
 91        text("STEP_UP.click_count = {STEP_UP.click_count}")
 92
 93        separator_text("with_font")
 94
 95        // The default font is the same as GetFont() — change to any loaded
 96        // font handle to switch scope-local typography.
 97        text("with_font(...) swaps the active font for one scope.")
 98        with_font(GetFont()) {
 99            text(FONT_DEMO, (text = "Inside the with_font scope."))
100        }
101    }
102
103    end_of_frame()
104    Render()
105    var w, h : int
106    live_get_framebuffer_size(w, h)
107    glViewport(0, 0, w, h)
108    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
109    glClear(GL_COLOR_BUFFER_BIT)
110    live_imgui_render()
111
112    live_end_frame()
113}
114
115[export]
116def shutdown() {
117    live_imgui_shutdown()
118    live_destroy_window()
119}
120
121[export]
122def main() {
123    init()
124    while (!exit_requested()) {
125        update()
126        maybe_collect_gc()
127    }
128    shutdown()
129}

8.19.12.1.1. Requires

One extra module beyond the baseline boost layer:

  • imgui/imgui_scope_builtin — defines the four with_* scope wrappers.

8.19.12.1.2. Why scope wrappers

Raw BeginDisabled / EndDisabled pairs work, but the v2 boost lint flags them as “invisible state changes” — if the source has BeginDisabled(...) somewhere and the matching EndDisabled() six branches deep, a reader has to trace control flow to see whether the disable will pop correctly on early-return paths. The scope wrapper:

  • Couples the push/pop into a block — exit through any path pops.

  • Reads as a single statement — the disabled argument is right at the top.

  • Survives return, break, continue in the body — block exit always pops the scope.

8.19.12.1.3. with_disabled

The argument is the disabled bool — true greys out the block. The tutorial drives this with an ENABLED_MASTER checkbox and passes !ENABLED_MASTER.value so the checkbox’s natural “enable child group” reading produces the right semantics.

8.19.12.1.4. with_button_repeat

When true, holding a button inside the block fires click events at ImGui’s repeat rate (configured by KeyRepeatDelay and KeyRepeatRate in ImGuiIO). Use it for stepper buttons (+, -) where hold-to-increment is the natural interaction. Outside this scope, buttons fire one click per press regardless of hold duration.

The tutorial’s STEP_UP.click_count climbs visibly while the cursor holds down on the + button in the recording — each repeat fires the click handler.

8.19.12.1.5. with_font

Push a different ImFont? for the block’s render — the default font is whatever GetFont() returns. Combine with load_daslang_font (see imgui/imgui_theme_daslang) to load a custom TTF and scope-swap it for specific UI sections (code editors, narration boxes, etc.).

The tutorial’s with_font(GetFont()) is a no-op for demonstration — the rendered text is the active font. Production code would pass a specific ImFont? returned from a prior AddFontFromFileTTF call.

8.19.12.1.6. with_clip_rect

The fourth scope wrapper isn’t exercised in this tutorial — see modules/dasImgui/examples/features/clip_rect.das for a per-frame clipping demo. with_clip_rect(min, max, isect, blk) is the safest way to install a custom clip rectangle around custom-rendered content (drawlist primitives, images, manual layout) since the scope guarantees the prior clip rect is restored on exit.

8.19.12.1.7. Combining wrappers

The wrappers compose cleanly — nest them to layer scopes:

checkbox(FEATURE_ENABLED, (text = "Feature on"))
with_disabled(!FEATURE_ENABLED.value) {
    with_button_repeat(true) {
        button(STEP_UP, (text = "+"))
        button(STEP_DN, (text = "-"))
    }
}

Steppers fire repeat clicks when held, but the whole pair greys out when the feature flag is off. Each wrapper’s exit pops its own scope; ImGui’s internal stacks handle the LIFO ordering.

See also

Full source: modules/dasImgui/examples/tutorial/with_disabled.das

Integration tests: modules/dasImgui/tests/test_disabled_block.das, modules/dasImgui/tests/test_button_repeat.das, modules/dasImgui/tests/test_font_stack.das.

Companion tutorials: With id, With style, With tab stop — other with_* scope helpers.

Boost macros — the macro layer.