8.18.1.10. With style

ImGui’s style stack lets you change a color or metric for a scoped region of the UI — push it once, pop it when you’re done. Hand-balancing the push/pop pairs is bug-prone (miss a pop and EndFrame asserts), and heterogeneous tuples can’t go through daslang’s uniform-element-type varargs, so the boost layer ships with_style((key, value), ...) { ... } as a [call_macro]. Each (key, value) tuple is dispatched at compile-time — ImGuiCol keys route to PushStyleColor, ImGuiStyleVar keys route to PushStyleVar — and a single bulk pop_style_n undoes them all at block exit.

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

8.18.1.10.1. Walkthrough

  1options gen2
  2options _comment_hygiene = true
  3
  4require imgui
  5require imgui_app
  6require opengl/opengl_boost
  7require live/glfw_live
  8require live/live_api
  9require live/live_commands
 10require live/live_vars
 11require live_host
 12require imgui/imgui_live
 13require imgui/imgui_boost_runtime
 14require imgui/imgui_boost_v2
 15require imgui/imgui_widgets_builtin
 16require imgui/imgui_containers_builtin
 17require imgui/imgui_style_builtin
 18require imgui/imgui_visual_aids
 19
 20// =============================================================================
 21// TUTORIAL: with_style — scoped color/metric overrides for a sub-tree.
 22//
 23// ImGui's style stack is one of its main customization knobs: PushStyleColor
 24// changes a color until the matching PopStyleColor; PushStyleVar changes a
 25// metric (rounding, padding, spacing, ...) until the matching PopStyleVar.
 26// Hand-balancing the pushes and pops is bug-prone — miss a pop and the
 27// next frame asserts on EndFrame. The `with_style((key, val), ...) { ... }`
 28// boost macro brackets the push/pop pair around a block:
 29//
 30//   with_style((ImGuiCol.Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f)),
 31//              (ImGuiStyleVar.FrameRounding, 8.0f)) {
 32//       button(STYLED_BTN, (text = "Red rounded"))
 33//   }
 34//
 35// Each `(key, value)` tuple is dispatched at compile time by overload
 36// resolution — ImGuiCol keys go to PushStyleColor, ImGuiStyleVar keys to
 37// PushStyleVar. After the block, the matching pops fire in one bulk call.
 38//
 39// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/with_style.das
 40// LIVE:       daslang-live modules/dasImgui/examples/tutorial/with_style.das
 41//
 42// DRIVE (when running live):
 43//   curl -X POST -d '{"name":"imgui_snapshot"}'                                                       localhost:9090/command
 44//   curl -X POST -d '{"name":"imgui_click","args":{"target":"STYLE_WIN/STYLED_BTN"}}'                  localhost:9090/command
 45//   curl -X POST -d '{"name":"imgui_click","args":{"target":"STYLE_WIN/NESTED_STYLED_BTN"}}'           localhost:9090/command
 46// =============================================================================
 47
 48[export]
 49def init() {
 50    live_create_window("dasImgui with_style tutorial", 720, 520)
 51    live_imgui_init(live_window)
 52    let io & = unsafe(GetIO())
 53    GetStyle().FontScaleMain = 1.5
 54}
 55
 56[export]
 57def update() {
 58    if (!live_begin_frame()) return
 59    begin_frame()
 60
 61    ImGui_ImplGlfw_NewFrame()
 62    apply_synth_io_override()
 63    NewFrame()
 64
 65    SetNextWindowPos(ImVec2(30.0f, 30.0f), ImGuiCond.FirstUseEver)
 66    SetNextWindowSize(ImVec2(640.0f, 440.0f), ImGuiCond.FirstUseEver)
 67    window(STYLE_WIN, (text = "with_style", closable = false,
 68                       flags = ImGuiWindowFlags.None)) {
 69        text("Mixed colors + var, single block:")
 70        // A full button restyle sets all three interaction states - base,
 71        // hovered, active - so the color holds while the pointer is over it.
 72        with_style((ImGuiCol.Button, ImVec4(0.85f, 0.20f, 0.20f, 1.0f)),
 73                   (ImGuiCol.ButtonHovered, ImVec4(0.95f, 0.30f, 0.30f, 1.0f)),
 74                   (ImGuiCol.ButtonActive, ImVec4(0.70f, 0.12f, 0.12f, 1.0f)),
 75                   (ImGuiStyleVar.FrameRounding, 8.0f)) {
 76            button(STYLED_BTN, (text = "Red rounded"))
 77        }
 78
 79        separator(WS_SEP_1)
 80        text("Nested with_style - inner stacks on outer:")
 81        with_style((ImGuiCol.Text, ImVec4(1.0f, 0.95f, 0.30f, 1.0f))) {
 82            text("Yellow text inherited from outer scope.")
 83            with_style((ImGuiCol.Button, ImVec4(0.20f, 0.45f, 0.85f, 1.0f)),
 84                       (ImGuiCol.ButtonHovered, ImVec4(0.30f, 0.55f, 0.95f, 1.0f)),
 85                       (ImGuiCol.ButtonActive, ImVec4(0.12f, 0.35f, 0.70f, 1.0f)),
 86                       (ImGuiStyleVar.FrameRounding, 4.0f)) {
 87                // Both the outer yellow text and the inner blue/rounded
 88                // button are in scope.
 89                button(NESTED_STYLED_BTN, (text = "Blue button + yellow text"))
 90            }
 91        }
 92
 93        separator(WS_SEP_2)
 94        text("After with_style - baseline style restored.")
 95        // If push/pop counts were off, this button would render with stale
 96        // color/rounding, and ImGui asserts on a stack imbalance at EndFrame
 97        // (crash). The boost macro guarantees the bulk pop_style_n always fires.
 98        button(UNSTYLED_BTN, (text = "Plain button"))
 99    }
100
101    end_of_frame()
102    Render()
103    var w, h : int
104    live_get_framebuffer_size(w, h)
105    glViewport(0, 0, w, h)
106    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
107    glClear(GL_COLOR_BUFFER_BIT)
108    live_imgui_render()
109
110    live_end_frame()
111}
112
113[export]
114def shutdown() {
115    live_imgui_shutdown()
116    live_destroy_window()
117}
118
119[export]
120def main() {
121    init()
122    while (!exit_requested()) {
123        update()
124    }
125    shutdown()
126}

8.18.1.10.1.1. Requires

One extra module on top of the baseline boost layer:

  • imgui/imgui_style_builtin — the with_style [call_macro] plus the push_style_one / pop_style_n primitives it lowers to.

8.18.1.10.1.2. Single block, mixed types

Each tuple in the with_style(...) argument list is independent — colors and metric overrides mix freely:

with_style((ImGuiCol.Button, ImVec4(0.85f, 0.20f, 0.20f, 1.0f)),
           (ImGuiCol.ButtonHovered, ImVec4(0.95f, 0.30f, 0.30f, 1.0f)),
           (ImGuiCol.ButtonActive, ImVec4(0.70f, 0.12f, 0.12f, 1.0f)),
           (ImGuiStyleVar.FrameRounding, 8.0f)) {
    button(STYLED_BTN, (text = "Red rounded"))
}

A full button restyle pushes all three interaction states — Button, ButtonHovered, ButtonActive — so the color holds while the pointer is over it (push only Button and the default hover/active theme colors show through on interaction). Here they mix with a rounding metric in one block.

The macro emits one push_style_one(key, val) per tuple in source order, then invoke(blk), then a single pop_style_n(N) that pops the matching count in two bulk ImGui calls (one PopStyleColor for the color pushes, one PopStyleVar for the metric pushes).

8.18.1.10.1.3. Nesting

Nested with_style blocks stack: the inner block adds to the outer’s overrides without disturbing them. When the inner block exits, the outer scope is restored:

with_style((ImGuiCol.Text, ImVec4(1.0f, 0.95f, 0.30f, 1.0f))) {
    Text("Yellow text inherited from outer scope.")
    with_style((ImGuiCol.Button, ImVec4(0.20f, 0.45f, 0.85f, 1.0f)),
               (ImGuiStyleVar.FrameRounding, 4.0f)) {
        // Yellow text + blue button + 4-radius rounding all active.
        button(NESTED_STYLED_BTN, (text = "Blue button + yellow text"))
    }
    // Back to: yellow text, default button color/rounding.
}

8.18.1.10.1.4. Pop balance

After every with_style block exits, the baseline style is restored — the underlying g_style_pop_stack tracks per-push kind tags so the bulk pop fires the right number of PopStyleColor / PopStyleVar calls. Miss a pop and ImGui asserts at EndFrame; the macro is the only API surface that pushes, so user code can’t accidentally leak pushes past the block boundary.

8.18.1.10.1.5. Standalone vs live

Same convention as previous tutorials.

8.18.1.10.1.6. Driving from outside

with_style is purely structural — there’s no per-block state to set. Drive the buttons inside instead:

curl -X POST -d '{"name":"imgui_click","args":{"target":"STYLE_WIN/STYLED_BTN"}}' \
     localhost:9090/command
curl -X POST -d '{"name":"imgui_click","args":{"target":"STYLE_WIN/NESTED_STYLED_BTN"}}' \
     localhost:9090/command

8.18.1.10.1.7. Next steps

Widget identity comes next — with_id to disambiguate widgets that share the same name within an ImGui ID scope, plus the id=/path= sugar that lets the boost layer steer the registry path without restructuring the call hierarchy.

See also

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

Richer reference: modules/dasImgui/examples/features/style_override.das — the features-side demo with the same surface plus a baseline-restore check.

Integration test: modules/dasImgui/tests/test_style_with_style.das.

Previous tutorial: Docking

Boost macros — the macro layer.