dear-imgui-rs 0.14.0

High-level Rust bindings to Dear ImGui v1.92.7 with docking, WGPU/GL backends, and extensions (ImPlot/ImPlot3D, ImNodes, ImGuizmo, file browser, reflection-based UI)
Documentation
use super::super::color::ImColor32;
use super::super::counts::{
    DrawCornerFlags, DrawNgonSegmentCount, DrawSegmentCount, PolylineFlags,
};
use super::super::util::draw_list_counts;
use super::DrawListMut;
use crate::sys;
use std::marker::PhantomData;

struct TestDrawList {
    shared: *mut sys::ImDrawListSharedData,
    raw: *mut sys::ImDrawList,
}

impl TestDrawList {
    fn new() -> Self {
        let shared = unsafe { sys::ImDrawListSharedData_ImDrawListSharedData() };
        assert!(!shared.is_null());
        let raw = unsafe { sys::ImDrawList_ImDrawList(shared) };
        assert!(!raw.is_null());
        Self { shared, raw }
    }

    fn draw_list(&self) -> DrawListMut<'static> {
        DrawListMut {
            draw_list: self.raw,
            _phantom: PhantomData,
        }
    }

    fn path_size(&self) -> i32 {
        unsafe { (*self.raw)._Path.Size }
    }

    fn clip_stack_size(&self) -> i32 {
        unsafe { (*self.raw)._ClipRectStack.Size }
    }
}

impl Drop for TestDrawList {
    fn drop(&mut self) {
        unsafe {
            sys::ImDrawList_destroy(self.raw);
            sys::ImDrawListSharedData_destroy(self.shared);
        }
    }
}

fn assert_panics_without_buffer_change(
    fixture: &TestDrawList,
    f: impl FnOnce(&DrawListMut<'static>),
) {
    let draw_list = fixture.draw_list();
    let before = draw_list_counts(fixture.raw);
    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&draw_list)));
    assert!(result.is_err());
    assert_eq!(draw_list_counts(fixture.raw), before);
}

#[test]
fn direct_draw_inputs_validate_before_ffi() {
    let fixture = TestDrawList::new();

    assert_panics_without_buffer_change(&fixture, |draw_list| {
        draw_list.add_line_h(f32::NAN, 1.0, 0.0, ImColor32::WHITE, 1.0);
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| {
        draw_list.add_line_v(0.0, 0.0, 1.0, ImColor32::WHITE, 0.0);
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| {
        draw_list.add_quad(
            [f32::NAN, 0.0],
            [1.0, 0.0],
            [1.0, 1.0],
            [0.0, 1.0],
            ImColor32::WHITE,
            1.0,
        );
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| {
        draw_list.add_ellipse(
            [0.0, 0.0],
            [-1.0, 4.0],
            ImColor32::WHITE,
            0.0,
            DrawSegmentCount::AUTO,
            1.0,
        );
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| {
        draw_list.add_bezier_quadratic(
            [0.0, 0.0],
            [1.0, 1.0],
            [2.0, 0.0],
            ImColor32::WHITE,
            1.0,
            DrawSegmentCount::count(0),
        );
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| {
        draw_list.add_image_rounded(
            crate::texture::TextureId::new(1),
            [0.0, 0.0],
            [16.0, 16.0],
            [0.0, 0.0],
            [1.0, 1.0],
            ImColor32::WHITE,
            f32::INFINITY,
            DrawCornerFlags::ALL,
        );
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| {
        draw_list.path_rect(
            [0.0, 0.0],
            [1.0, 1.0],
            1.0,
            DrawCornerFlags::from_bits_retain(sys::ImDrawFlags_Closed as u32),
        );
    });
}

#[test]
fn path_inputs_validate_before_path_mutation() {
    let fixture = TestDrawList::new();
    let draw_list = fixture.draw_list();

    draw_list.path_line_to([1.0, 1.0]);
    let path_size = fixture.path_size();

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        draw_list.path_line_to([f32::NAN, 2.0]);
    }));
    assert!(result.is_err());
    assert_eq!(fixture.path_size(), path_size);

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        draw_list.path_arc_to([0.0, 0.0], -1.0, 0.0, 1.0, DrawSegmentCount::AUTO);
    }));
    assert!(result.is_err());
    assert_eq!(fixture.path_size(), path_size);

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        draw_list.path_arc_to_fast([0.0, 0.0], 1.0, 0, 13);
    }));
    assert!(result.is_err());
    assert_eq!(fixture.path_size(), path_size);

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        draw_list.path_bezier_quadratic_curve_to(
            [2.0, 2.0],
            [3.0, 3.0],
            DrawSegmentCount::count(0),
        );
    }));
    assert!(result.is_err());
    assert_eq!(fixture.path_size(), path_size);

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        draw_list.path_stroke(
            ImColor32::WHITE,
            PolylineFlags::from_bits_retain(sys::ImDrawFlags_RoundCornersTopLeft as u32),
            1.0,
        );
    }));
    assert!(result.is_err());
    assert_eq!(fixture.path_size(), path_size);

    draw_list.path_clear();
    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        draw_list.path_bezier_cubic_curve_to(
            [2.0, 2.0],
            [3.0, 3.0],
            [4.0, 4.0],
            DrawSegmentCount::AUTO,
        );
    }));
    assert!(result.is_err());
    assert_eq!(fixture.path_size(), 0);
}

#[test]
fn builder_inputs_validate_before_ffi() {
    let fixture = TestDrawList::new();

    assert_panics_without_buffer_change(&fixture, |draw_list| {
        let _ = draw_list
            .add_line([0.0, 0.0], [1.0, 1.0], ImColor32::WHITE)
            .thickness(0.0);
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| {
        let _ = draw_list.add_circle([0.0, 0.0], -1.0, ImColor32::WHITE);
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| {
        let _ = draw_list
            .add_rect([0.0, 0.0], [1.0, 1.0], ImColor32::WHITE)
            .rounding(f32::NAN);
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| {
        let _ = draw_list.add_polyline(vec![[0.0, 0.0], [f32::INFINITY, 1.0]], ImColor32::WHITE);
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| {
        let _ = draw_list
            .add_bezier_curve(
                [0.0, 0.0],
                [1.0, 1.0],
                [2.0, 1.0],
                [3.0, 0.0],
                ImColor32::WHITE,
            )
            .num_segments(DrawSegmentCount::count(i32::MAX as usize + 1));
    });
}

#[test]
fn draw_segment_counts_reject_invalid_values_before_ffi() {
    assert!(DrawNgonSegmentCount::new(2).is_none());
    assert!(DrawNgonSegmentCount::new(3).is_some());
    assert!(DrawNgonSegmentCount::new(i32::MAX as usize + 1).is_none());

    assert_eq!(DrawSegmentCount::AUTO.get(), None);
    assert_eq!(
        DrawSegmentCount::new(3).and_then(DrawSegmentCount::get),
        Some(3)
    );
    assert_eq!(DrawSegmentCount::new(0), None);
    assert_eq!(DrawSegmentCount::new(i32::MAX as usize + 1), None);

    assert!(std::panic::catch_unwind(|| DrawSegmentCount::count(0)).is_err());
    assert!(std::panic::catch_unwind(|| DrawSegmentCount::count(i32::MAX as usize + 1)).is_err());
}

#[test]
fn primitive_reserve_counts_reject_overflow_before_ffi() {
    let fixture = TestDrawList::new();

    assert_panics_without_buffer_change(&fixture, |draw_list| unsafe {
        draw_list.prim_reserve(i32::MAX as usize + 1, 0);
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| unsafe {
        draw_list.prim_reserve(0, i32::MAX as usize + 1);
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| unsafe {
        draw_list.prim_unreserve(i32::MAX as usize + 1, 0);
    });
    assert_panics_without_buffer_change(&fixture, |draw_list| unsafe {
        draw_list.prim_unreserve(0, i32::MAX as usize + 1);
    });
}

#[test]
fn text_and_clip_inputs_validate_before_ffi() {
    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);
    }
    let _ = ctx.font_atlas_mut().build();
    let _ = ctx.set_ini_filename::<std::path::PathBuf>(None);

    let ui = ctx.frame();
    let draw_list = ui.get_window_draw_list();
    let raw_draw_list = draw_list.draw_list;
    let font = ui.current_font();
    let before = draw_list_counts(raw_draw_list);
    let clip_stack_size = unsafe { (*raw_draw_list)._ClipRectStack.Size };

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        draw_list.add_text([f32::NAN, 0.0], ImColor32::WHITE, "hello");
    }));
    assert!(result.is_err());
    assert_eq!(draw_list_counts(raw_draw_list), before);

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        draw_list.add_text_with_font(font, -1.0, [0.0, 0.0], ImColor32::WHITE, "hello", 0.0, None);
    }));
    assert!(result.is_err());
    assert_eq!(draw_list_counts(raw_draw_list), before);

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        draw_list.push_clip_rect([f32::NAN, 0.0], [1.0, 1.0], false);
    }));
    assert!(result.is_err());
    assert_eq!(
        unsafe { (*raw_draw_list)._ClipRectStack.Size },
        clip_stack_size
    );
}

#[test]
fn raw_draw_list_clip_helper_reads_stack_without_mutation() {
    let fixture = TestDrawList::new();
    let before = fixture.clip_stack_size();
    let draw_list = fixture.draw_list();

    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        draw_list.push_clip_rect([0.0, 0.0], [f32::INFINITY, 1.0], false);
    }));

    assert!(result.is_err());
    assert_eq!(fixture.clip_stack_size(), before);
}