dear-imgui-sys 0.16.0-alpha.2

Low-level FFI bindings to Dear ImGui v1.92.9b (docking branch) via cimgui (C API)
Documentation
#![cfg(all(not(target_arch = "wasm32"), dear_imgui_rs_native_symbols))]

use std::ffi::{CStr, CString, c_void};

use dear_imgui_sys as sys;

fn format_value<T>(data_type: sys::ImGuiDataType, value: &T, format: &str) -> String {
    format_value_with_capacity(data_type, value, format, 1024).1
}

fn format_value_with_capacity<T>(
    data_type: sys::ImGuiDataType,
    value: &T,
    format: &str,
    capacity: usize,
) -> (i32, String) {
    assert!(
        capacity > 0,
        "the test helper requires room for a terminator"
    );
    let format = CString::new(format).expect("test format must not contain NUL bytes");
    let mut output = vec![0_i8; capacity];
    let written = unsafe {
        sys::igDataTypeFormatString(
            output.as_mut_ptr(),
            output.len().try_into().unwrap(),
            data_type,
            std::ptr::from_ref(value).cast::<c_void>(),
            format.as_ptr(),
        )
    };
    assert!(written >= 0);
    let output = unsafe { CStr::from_ptr(output.as_ptr()) }
        .to_str()
        .expect("formatted output must remain UTF-8")
        .to_owned();
    (written, output)
}

fn apply_value<T>(data_type: sys::ImGuiDataType, value: &mut T, input: &str, format: &str) -> bool {
    let input = CString::new(input).expect("test input must not contain NUL bytes");
    let format = CString::new(format).expect("test format must not contain NUL bytes");
    unsafe {
        sys::igDataTypeApplyFromText(
            input.as_ptr(),
            data_type,
            std::ptr::from_mut(value).cast::<c_void>(),
            format.as_ptr(),
            std::ptr::null_mut(),
        )
    }
}

#[test]
fn formatting_keeps_utf8_decoration_outside_printf() {
    let decorated = format_value(sys::ImGuiDataType_Double, &12.5_f64, "温度 %.2f °C");
    assert_eq!(decorated, "温度 12.50 °C");

    let mut parsed = 0.0_f64;
    assert!(apply_value(
        sys::ImGuiDataType_Double,
        &mut parsed,
        &decorated,
        "温度 %.2f °C"
    ));
    assert_eq!(parsed, 12.5);

    assert_eq!(
        format_value(sys::ImGuiDataType_S32, &7_i32, "load %% complete"),
        "load % complete"
    );
}

#[test]
fn numeric_suffixes_round_trip_without_becoming_part_of_the_value() {
    let mut decimal = 0_i32;
    let decimal_text = format_value(sys::ImGuiDataType_S32, &5_i32, "%d123");
    assert_eq!(decimal_text, "5123");
    assert!(apply_value(
        sys::ImGuiDataType_S32,
        &mut decimal,
        &decimal_text,
        "%d123"
    ));
    assert_eq!(decimal, 5);

    let mut hexadecimal = 0_u32;
    let hexadecimal_text = format_value(sys::ImGuiDataType_U32, &10_u32, "%xF");
    assert_eq!(hexadecimal_text, "aF");
    assert!(apply_value(
        sys::ImGuiDataType_U32,
        &mut hexadecimal,
        &hexadecimal_text,
        "%xF"
    ));
    assert_eq!(hexadecimal, 10);

    let mut floating = 0.0_f32;
    let floating_text = format_value(sys::ImGuiDataType_Float, &1.0_f32, "%fe3");
    assert_eq!(floating_text, "1.000000e3");
    assert!(apply_value(
        sys::ImGuiDataType_Float,
        &mut floating,
        &floating_text,
        "%fe3"
    ));
    assert_eq!(floating, 1.0);
}

#[test]
fn parsing_clamps_overflow_and_rejects_invalid_values_without_writing() {
    let mut signed = 0_i8;
    assert!(apply_value(
        sys::ImGuiDataType_S8,
        &mut signed,
        "1000",
        "%d"
    ));
    assert_eq!(signed, i8::MAX);

    let mut unsigned = 7_u8;
    assert!(!apply_value(
        sys::ImGuiDataType_U8,
        &mut unsigned,
        "-1",
        "%u"
    ));
    assert_eq!(unsigned, 7);

    let mut not_a_number = 3.0_f32;
    assert!(!apply_value(
        sys::ImGuiDataType_Float,
        &mut not_a_number,
        "nan",
        "%f"
    ));
    assert_eq!(not_a_number, 3.0);

    let mut infinity = 0.0_f32;
    assert!(apply_value(
        sys::ImGuiDataType_Float,
        &mut infinity,
        "inf",
        "%f"
    ));
    assert_eq!(infinity, f32::MAX);
}

#[test]
fn every_supported_numeric_carrier_formats_and_parses_with_its_exact_type() {
    macro_rules! assert_round_trip {
        ($data_type:expr, $value_type:ty, $value:expr, $format:expr, $expected:expr) => {{
            let value: $value_type = $value;
            let text = format_value($data_type, &value, $format);
            assert_eq!(text, $expected);
            let mut parsed = <$value_type>::default();
            assert!(apply_value($data_type, &mut parsed, &text, $format));
            assert_eq!(parsed, value);
        }};
    }

    assert_round_trip!(sys::ImGuiDataType_S8, i8, -12, "%d", "-12");
    assert_round_trip!(sys::ImGuiDataType_U8, u8, 250, "%u", "250");
    assert_round_trip!(sys::ImGuiDataType_S16, i16, -1234, "%d", "-1234");
    assert_round_trip!(sys::ImGuiDataType_U16, u16, 60_000, "%u", "60000");
    assert_round_trip!(
        sys::ImGuiDataType_S32,
        i32,
        -2_000_000_000,
        "%d",
        "-2000000000"
    );
    assert_round_trip!(
        sys::ImGuiDataType_U32,
        u32,
        4_000_000_000,
        "%u",
        "4000000000"
    );

    #[cfg(target_env = "msvc")]
    let (signed_64_format, unsigned_64_format) = ("%I64d", "%I64u");
    #[cfg(not(target_env = "msvc"))]
    let (signed_64_format, unsigned_64_format) = ("%lld", "%llu");

    assert_round_trip!(
        sys::ImGuiDataType_S64,
        i64,
        i64::MIN,
        signed_64_format,
        "-9223372036854775808"
    );
    assert_round_trip!(
        sys::ImGuiDataType_U64,
        u64,
        u64::MAX,
        unsigned_64_format,
        "18446744073709551615"
    );
    assert_round_trip!(sys::ImGuiDataType_Float, f32, 1.5, "%.1f", "1.5");
    assert_round_trip!(sys::ImGuiDataType_Double, f64, -2.25, "%.2f", "-2.25");
}

#[test]
fn numeric_buffers_handle_the_safe_contract_limits_and_fail_closed_beyond_them() {
    let maximum_fixed = format_value(sys::ImGuiDataType_Double, &f64::MAX, "%.99f");
    assert_eq!(maximum_fixed.len(), 409);
    assert!(maximum_fixed.ends_with(&format!(".{:0<99}", "")));

    let (written, truncated) =
        format_value_with_capacity(sys::ImGuiDataType_S32, &123_i32, "prefix %d suffix", 8);
    assert_eq!(written, 7);
    assert_eq!(truncated, "prefix ");

    let mut accepted = 0_u64;
    assert!(apply_value(
        sys::ImGuiDataType_U64,
        &mut accepted,
        &"9".repeat(511),
        "%llu"
    ));
    assert_eq!(accepted, u64::MAX);

    let mut rejected = 7_u64;
    assert!(!apply_value(
        sys::ImGuiDataType_U64,
        &mut rejected,
        &"9".repeat(512),
        "%llu"
    ));
    assert_eq!(rejected, 7);
}