dear-imgui-rs 0.16.0

High-level Rust bindings to Dear ImGui v1.92.9b with docking, WGPU/GL backends, and extensions (ImPlot/ImPlot3D, ImNodes, ImGuizmo, file browser, reflection-based UI)
Documentation
fn setup_context() -> crate::Context {
    let mut ctx = crate::Context::create();
    ctx.font_atlas()
        .try_claim_legacy_renderer()
        .expect("legacy renderer font atlas should be available")
        .build();
    ctx.io_mut().set_display_size([128.0, 128.0]);
    ctx.io_mut().set_delta_time(1.0 / 60.0);
    ctx
}

#[derive(Clone, Copy, Debug)]
struct DisabledNativeState {
    alpha: f32,
    disabled_alpha: f32,
    alpha_backup: f32,
    current_item_flags: i32,
    item_flags_stack_size: i32,
    style_var_stack_size: i32,
    disabled_stack_size: i16,
}

fn disabled_native_state(ui: &crate::Ui) -> DisabledNativeState {
    let context = ui.context_raw();
    ui.binding().with_bound_context(|| unsafe {
        DisabledNativeState {
            alpha: (*context).Style.Alpha,
            disabled_alpha: (*context).Style.DisabledAlpha,
            alpha_backup: (*context).DisabledAlphaBackup,
            current_item_flags: (*context).CurrentItemFlags,
            item_flags_stack_size: (*context).ItemFlagsStack.Size,
            style_var_stack_size: (*context).StyleVarStack.Size,
            disabled_stack_size: (*context).DisabledStackSize,
        }
    })
}

fn assert_alpha_eq(actual: f32, expected: f32) {
    let tolerance = f32::EPSILON * expected.abs().max(1.0) * 4.0;
    assert!(
        (actual - expected).abs() <= tolerance,
        "expected alpha {expected}, got {actual}"
    );
}

fn assert_disabled_state_restored(actual: DisabledNativeState, baseline: DisabledNativeState) {
    assert_alpha_eq(actual.alpha, baseline.alpha);
    assert_eq!(actual.current_item_flags, baseline.current_item_flags);
    assert_eq!(actual.item_flags_stack_size, baseline.item_flags_stack_size);
    assert_eq!(actual.style_var_stack_size, baseline.style_var_stack_size);
    assert_eq!(actual.disabled_stack_size, baseline.disabled_stack_size);
}

#[test]
fn with_button_repeat_pops_after_panic() {
    let mut ctx = setup_context();
    let ui = ctx.frame();
    let raw_ctx = unsafe { crate::sys::igGetCurrentContext() };
    assert!(!raw_ctx.is_null());
    let initial_stack_size = unsafe { (*raw_ctx).ItemFlagsStack.Size };

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        ui.with_button_repeat(true, || {
            assert_eq!(
                unsafe { (*raw_ctx).ItemFlagsStack.Size },
                initial_stack_size + 1
            );
            panic!("forced panic while button repeat is pushed");
        });
    }));

    assert!(result.is_err());
    assert_eq!(
        unsafe { (*raw_ctx).ItemFlagsStack.Size },
        initial_stack_size
    );
}

#[test]
fn disabled_scopes_model_only_the_layer_that_owns_alpha_restoration() {
    let mut ctx = setup_context();
    let ui = ctx.frame();

    ui.window("disabled_alpha_ownership").build(|| {
        let baseline = disabled_native_state(ui);

        let disabled_false = ui.begin_disabled_with_cond(false);
        let after_false = disabled_native_state(ui);
        assert_alpha_eq(after_false.alpha, baseline.alpha);
        assert_eq!(after_false.alpha_backup, baseline.alpha_backup);
        assert_eq!(after_false.current_item_flags, baseline.current_item_flags);
        assert_eq!(
            after_false.item_flags_stack_size,
            baseline.item_flags_stack_size + 1
        );
        assert_eq!(
            after_false.disabled_stack_size,
            baseline.disabled_stack_size + 1
        );
        drop(disabled_false);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);

        let outer = ui.begin_disabled();
        let after_outer = disabled_native_state(ui);
        assert_alpha_eq(after_outer.alpha, baseline.alpha * baseline.disabled_alpha);
        assert_alpha_eq(after_outer.alpha_backup, baseline.alpha);

        let inner_true = ui.begin_disabled();
        let after_inner_true = disabled_native_state(ui);
        assert_alpha_eq(after_inner_true.alpha, after_outer.alpha);
        assert_alpha_eq(after_inner_true.alpha_backup, after_outer.alpha_backup);
        drop(inner_true);
        assert_alpha_eq(disabled_native_state(ui).alpha, after_outer.alpha);

        let inner_false = ui.begin_disabled_with_cond(false);
        let after_inner_false = disabled_native_state(ui);
        assert_alpha_eq(after_inner_false.alpha, after_outer.alpha);
        assert_alpha_eq(after_inner_false.alpha_backup, after_outer.alpha_backup);
        drop(inner_false);
        assert_alpha_eq(disabled_native_state(ui).alpha, after_outer.alpha);
        drop(outer);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);

        let outer_false = ui.begin_disabled_with_cond(false);
        let inner_true = ui.begin_disabled();
        assert_alpha_eq(
            disabled_native_state(ui).alpha,
            baseline.alpha * baseline.disabled_alpha,
        );
        drop(inner_true);
        assert_alpha_eq(disabled_native_state(ui).alpha, baseline.alpha);
        drop(outer_false);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);
    });
}

#[test]
fn disabled_and_alpha_style_scopes_restore_in_both_lifo_nesting_directions() {
    let mut ctx = setup_context();
    let ui = ctx.frame();

    ui.window("disabled_alpha_lifo").build(|| {
        let baseline = disabled_native_state(ui);
        let overridden_alpha = 0.4;

        let alpha = ui.push_style_var(crate::StyleVar::Alpha(overridden_alpha));
        assert_alpha_eq(disabled_native_state(ui).alpha, overridden_alpha);
        let disabled = ui.begin_disabled();
        assert_alpha_eq(
            disabled_native_state(ui).alpha,
            overridden_alpha * baseline.disabled_alpha,
        );
        drop(disabled);
        assert_alpha_eq(disabled_native_state(ui).alpha, overridden_alpha);
        drop(alpha);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);

        let disabled = ui.begin_disabled();
        let disabled_alpha = baseline.alpha * baseline.disabled_alpha;
        assert_alpha_eq(disabled_native_state(ui).alpha, disabled_alpha);
        let alpha = ui.push_style_var(crate::StyleVar::Alpha(overridden_alpha));
        assert_alpha_eq(disabled_native_state(ui).alpha, overridden_alpha);
        drop(alpha);
        assert_alpha_eq(disabled_native_state(ui).alpha, disabled_alpha);
        drop(disabled);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);
    });
}

#[test]
fn disabled_and_alpha_style_scopes_reject_cross_stack_order_before_ffi_and_recover() {
    let mut ctx = setup_context();
    let ui = ctx.frame();

    ui.window("disabled_alpha_invalid_order").build(|| {
        let baseline = disabled_native_state(ui);

        let alpha = ui.push_style_var(crate::StyleVar::Alpha(0.4));
        let disabled = ui.begin_disabled();
        let before_failure = disabled_native_state(ui);
        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(alpha)));
        assert!(result.is_err());
        let after_failure = disabled_native_state(ui);
        assert_alpha_eq(after_failure.alpha, before_failure.alpha);
        assert_eq!(
            after_failure.item_flags_stack_size,
            before_failure.item_flags_stack_size
        );
        assert_eq!(
            after_failure.style_var_stack_size,
            before_failure.style_var_stack_size
        );
        assert_eq!(
            after_failure.disabled_stack_size,
            before_failure.disabled_stack_size
        );
        drop(disabled);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);

        let disabled = ui.begin_disabled();
        let alpha = ui.push_style_var(crate::StyleVar::Alpha(0.4));
        let before_failure = disabled_native_state(ui);
        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(disabled)));
        assert!(result.is_err());
        let after_failure = disabled_native_state(ui);
        assert_alpha_eq(after_failure.alpha, before_failure.alpha);
        assert_eq!(
            after_failure.item_flags_stack_size,
            before_failure.item_flags_stack_size
        );
        assert_eq!(
            after_failure.style_var_stack_size,
            before_failure.style_var_stack_size
        );
        assert_eq!(
            after_failure.disabled_stack_size,
            before_failure.disabled_stack_size
        );
        drop(alpha);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);
    });
}

#[test]
fn unrelated_disabled_and_style_scopes_remain_independent() {
    let mut ctx = setup_context();
    let ui = ctx.frame();

    ui.window("disabled_alpha_independent_scopes").build(|| {
        let baseline = disabled_native_state(ui);

        let disabled_false = ui.begin_disabled_with_cond(false);
        let alpha = ui.push_style_var(crate::StyleVar::Alpha(0.4));
        drop(disabled_false);
        drop(alpha);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);

        let alpha = ui.push_style_var(crate::StyleVar::Alpha(0.4));
        let disabled_false = ui.begin_disabled_with_cond(false);
        drop(alpha);
        drop(disabled_false);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);

        let disabled = ui.begin_disabled();
        let rounding = ui.push_style_var(crate::StyleVar::WindowRounding(7.0));
        drop(disabled);
        drop(rounding);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);

        let rounding = ui.push_style_var(crate::StyleVar::WindowRounding(7.0));
        let disabled = ui.begin_disabled();
        drop(rounding);
        drop(disabled);
        assert_disabled_state_restored(disabled_native_state(ui), baseline);
    });
}

#[test]
fn disabled_and_alpha_closure_scopes_restore_during_unwinding() {
    let mut ctx = setup_context();
    let ui = ctx.frame();

    ui.window("disabled_alpha_unwind").build(|| {
        let baseline = disabled_native_state(ui);

        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            ui.with_disabled(|| {
                let _alpha = ui.push_style_var(crate::StyleVar::Alpha(0.4));
                panic!("forced panic inside disabled outer scope");
            });
        }));
        assert!(result.is_err());
        assert_disabled_state_restored(disabled_native_state(ui), baseline);

        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            let _alpha = ui.push_style_var(crate::StyleVar::Alpha(0.4));
            ui.with_disabled(|| panic!("forced panic inside alpha outer scope"));
        }));
        assert!(result.is_err());
        assert_disabled_state_restored(disabled_native_state(ui), baseline);
    });
}