dear-imgui-rs 0.17.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
use crate::sys;

use super::*;

fn setup_context() -> crate::Context {
    let mut ctx = crate::Context::create();
    {
        let io = ctx.io_mut();
        io.set_display_size([128.0, 128.0]);
        io.set_delta_time(1.0 / 60.0);
    }
    ctx.font_atlas()
        .try_claim_legacy_renderer()
        .expect("legacy renderer font atlas should be available")
        .build();
    let _ = ctx.set_ini_filename::<std::path::PathBuf>(None);
    ctx
}

#[test]
fn multi_select_indexed_ends_scope_after_render_panic() {
    let mut ctx = setup_context();
    let raw_ctx = ctx.as_raw();

    let ui = ctx.frame();
    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        let _ = ui.window("multi_select_panic").build(|| {
            let mut selected = vec![false; 2];
            ui.multi_select_indexed(&mut selected, MultiSelectOptions::new(), |_, idx, _| {
                if idx == 0 {
                    panic!("forced panic while multi-select is active");
                }
            });
        });
    }));

    assert!(result.is_err());
    let _ = ui.window("multi_select_after_panic").build(|| {
        let result = ui.with_multi_select(MultiSelectOptions::new(), None, 0, |_| {});
        assert!(result.requests().is_empty());
    });
    unsafe {
        let imgui_ctx = raw_ctx as *const sys::ImGuiContext;
        assert!((*imgui_ctx).CurrentMultiSelect.is_null());
        assert_eq!((*imgui_ctx).MultiSelectTempDataStacked, 0);
    }
}

#[test]
fn with_multi_select_ends_scope_exactly_once() {
    let mut ctx = setup_context();
    let raw_ctx = ctx.as_raw();

    let ui = ctx.frame();
    let _ = ui.window("multi_select_explicit_end").build(|| {
        let result = ui.with_multi_select(MultiSelectOptions::new(), None, 0, |_| {});
        assert!(result.requests().is_empty());
    });

    unsafe {
        let imgui_ctx = raw_ctx as *const sys::ImGuiContext;
        assert!((*imgui_ctx).CurrentMultiSelect.is_null());
        assert_eq!((*imgui_ctx).MultiSelectTempDataStacked, 0);
    }
}

#[test]
fn with_multi_select_rejects_items_count_over_i32() {
    let mut ctx = setup_context();

    let ui = ctx.frame();
    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        let _ = ui.with_multi_select(
            MultiSelectOptions::new(),
            None,
            (i32::MAX as usize) + 1,
            |_| {},
        );
    }));

    assert!(result.is_err());
}

#[test]
fn result_remains_owned_after_a_later_scope_reuses_native_io() {
    let mut ctx = setup_context();

    let ui = ctx.frame();
    let _ = ui.window("multi_select_owned_result").build(|| {
        let first = ui.with_multi_select(MultiSelectOptions::new(), None, 0, |_| {});
        let first_copy = first.clone();

        let second = ui.with_multi_select(MultiSelectOptions::new(), None, 0, |scope| {
            scope.set_range_source_reset(true);
        });

        assert_eq!(first, first_copy);
        assert!(!first.range_source_reset());
        assert!(second.range_source_reset());
    });
}

#[test]
fn outer_scope_does_not_retain_io_pointer_across_nested_scope() {
    let mut ctx = setup_context();

    let ui = ctx.frame();
    let _ = ui.window("nested_multi_select").build(|| {
        let outer = ui.with_multi_select(MultiSelectOptions::new(), None, 0, |outer| {
            let inner = ui.with_multi_select(MultiSelectOptions::new(), None, 0, |_| {});
            assert!(inner.requests().is_empty());
            outer.set_range_source_reset(true);
        });

        assert!(outer.range_source_reset());
    });
}

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

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        let _ = ui.window("multi_select_focus_scope_order").build(|| {
            ui.with_multi_select(MultiSelectOptions::new(), None, 0, |_| {
                escaped_focus = Some(ui.push_focus_scope(ui.get_id("escaped-focus")));
            });
        });
    }));
    assert!(result.is_err());

    assert!(
        std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            ui.text("recovery is pending");
        }))
        .is_err()
    );

    drop(escaped_focus);
    let _ = ui.window("multi_select_focus_scope_recovered").build(|| {
        ui.text("scope tracker recovered");
    });
}