wilhelm_renderer_imgui 0.9.0

Dear ImGui integration for wilhelm_renderer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
//! Dear ImGui integration for the wilhelm_renderer stack.
//!
//! Sibling crate to `wilhelm_renderer`: both depend on `wilhelm_renderer_sys`
//! (which bundles GLFW + OpenGL loader), neither depends on the other. The
//! application combines them by handing the GLFW window pointer obtained from
//! `wilhelm_renderer` to `ImGui::new`.
//!
//! # Example
//!
//! ```ignore
//! use wilhelm_renderer::core::{App, Window, Color};
//! use wilhelm_renderer_imgui::ImGui;
//!
//! let window = Window::new("ImGui Example", 800, 600, Color::from_rgb(0.1, 0.1, 0.1));
//! let mut app = App::new(window);
//!
//! let imgui = ImGui::new(app.window.glfw_window_ptr());
//!
//! app.on_pre_render(move |_shapes, _renderer| {
//!     imgui.new_frame();
//!     imgui.begin("Debug", None, 0);
//!     imgui.text("Hello, ImGui!");
//!     imgui.end();
//! });
//!
//! app.on_render(move |_renderer| {
//!     imgui.render();
//! });
//!
//! app.run();
//! ```

pub use wilhelm_renderer_sys::glfw::GLFWwindow;

use std::ffi::CString;
use std::ptr;

// FFI declarations for the C wrapper
mod ffi {
    use std::os::raw::{c_char, c_float, c_int, c_ulong, c_void};
    use wilhelm_renderer_sys::glfw::GLFWwindow;

    unsafe extern "C" {
        // Context management
        pub fn imgui_create_context() -> *mut c_void;
        pub fn imgui_destroy_context(ctx: *mut c_void);

        // Backend initialization/shutdown
        pub fn imgui_init_for_glfw(window: *const GLFWwindow, install_callbacks: c_int) -> c_int;
        pub fn imgui_init_for_opengl3(glsl_version: *const c_char) -> c_int;
        pub fn imgui_shutdown_opengl3();
        pub fn imgui_shutdown_glfw();

        // Frame management
        pub fn imgui_new_frame();
        pub fn imgui_render();
        pub fn imgui_end_frame();

        // OpenGL3 backend rendering
        pub fn imgui_opengl3_render_draw_data();

        // IO access
        pub fn imgui_io_set_display_size(width: c_float, height: c_float);
        pub fn imgui_io_want_capture_mouse() -> c_int;
        pub fn imgui_io_want_capture_keyboard() -> c_int;
        pub fn imgui_io_set_config_flags(flags: c_int);

        // Basic widgets
        pub fn imgui_begin(name: *const c_char, p_open: *mut c_int, flags: c_int) -> c_int;
        pub fn imgui_end();
        pub fn imgui_text(text: *const c_char);
        pub fn imgui_button(label: *const c_char) -> c_int;
        pub fn imgui_checkbox(label: *const c_char, v: *mut c_int) -> c_int;
        pub fn imgui_slider_float(
            label: *const c_char,
            v: *mut c_float,
            v_min: c_float,
            v_max: c_float,
        ) -> c_int;
        pub fn imgui_slider_int(
            label: *const c_char,
            v: *mut c_int,
            v_min: c_int,
            v_max: c_int,
        ) -> c_int;
        pub fn imgui_input_float(label: *const c_char, v: *mut c_float) -> c_int;
        pub fn imgui_input_int(label: *const c_char, v: *mut c_int) -> c_int;
        pub fn imgui_input_text(
            label: *const c_char,
            buf: *mut c_char,
            buf_size: c_ulong,
            flags: c_int,
        ) -> c_int;
        pub fn imgui_color_edit3(label: *const c_char, col: *mut c_float) -> c_int;
        pub fn imgui_color_edit4(label: *const c_char, col: *mut c_float) -> c_int;
        pub fn imgui_same_line();
        pub fn imgui_separator();
        pub fn imgui_separator_text(label: *const c_char);
        pub fn imgui_spacing();
        pub fn imgui_dummy(width: c_float, height: c_float);
        pub fn imgui_indent(indent_w: c_float);
        pub fn imgui_unindent(indent_w: c_float);

        // Tree nodes
        pub fn imgui_tree_node(label: *const c_char) -> c_int;
        pub fn imgui_tree_pop();

        // Combo box
        pub fn imgui_begin_combo(
            label: *const c_char,
            preview_value: *const c_char,
            flags: c_int,
        ) -> c_int;
        pub fn imgui_end_combo();
        pub fn imgui_selectable(label: *const c_char, selected: c_int, flags: c_int) -> c_int;

        // Menu
        pub fn imgui_begin_main_menu_bar() -> c_int;
        pub fn imgui_end_main_menu_bar();
        pub fn imgui_begin_menu(label: *const c_char, enabled: c_int) -> c_int;
        pub fn imgui_end_menu();
        pub fn imgui_menu_item(
            label: *const c_char,
            shortcut: *const c_char,
            selected: c_int,
            enabled: c_int,
        ) -> c_int;

        // Tooltips
        pub fn imgui_set_tooltip(text: *const c_char);
        pub fn imgui_begin_tooltip() -> c_int;
        pub fn imgui_end_tooltip();

        // Popups
        pub fn imgui_begin_popup(str_id: *const c_char, flags: c_int) -> c_int;
        pub fn imgui_begin_popup_modal(
            name: *const c_char,
            p_open: *mut c_int,
            flags: c_int,
        ) -> c_int;
        pub fn imgui_end_popup();
        pub fn imgui_open_popup(str_id: *const c_char);
        pub fn imgui_close_current_popup();

        // Tables
        pub fn imgui_begin_table(str_id: *const c_char, column: c_int, flags: c_int) -> c_int;
        pub fn imgui_end_table();
        pub fn imgui_table_next_row();
        pub fn imgui_table_next_column() -> c_int;
        pub fn imgui_table_set_column_index(column_n: c_int) -> c_int;
        pub fn imgui_table_setup_column(
            label: *const c_char,
            flags: c_int,
            init_width_or_weight: c_float,
        );
        pub fn imgui_table_headers_row();

        // Columns (legacy)
        pub fn imgui_columns(count: c_int, id: *const c_char, border: c_int);
        pub fn imgui_next_column();

        // Style
        pub fn imgui_push_style_color(idx: c_int, r: c_float, g: c_float, b: c_float, a: c_float);
        pub fn imgui_pop_style_color(count: c_int);
        pub fn imgui_push_style_var_float(idx: c_int, val: c_float);
        pub fn imgui_push_style_var_vec2(idx: c_int, x: c_float, y: c_float);
        pub fn imgui_pop_style_var(count: c_int);

        // ID stack
        pub fn imgui_push_id_int(int_id: c_int);
        pub fn imgui_push_id_str(str_id: *const c_char);
        pub fn imgui_pop_id();

        // Utilities
        pub fn imgui_is_item_hovered() -> c_int;
        pub fn imgui_is_item_clicked(mouse_button: c_int) -> c_int;
        pub fn imgui_is_item_active() -> c_int;
        pub fn imgui_set_next_window_pos(x: c_float, y: c_float, cond: c_int);
        pub fn imgui_set_next_window_size(width: c_float, height: c_float, cond: c_int);

        // Demo window
        pub fn imgui_show_demo_window(p_open: *mut c_int);

        // DPI scaling
        pub fn imgui_get_dpi_scale(window: *const GLFWwindow) -> c_float;
        pub fn imgui_apply_dpi_scale(window: *const GLFWwindow);
    }
}

/// Window flags for `begin()`
pub mod window_flags {
    pub const NONE: i32 = 0;
    pub const NO_TITLE_BAR: i32 = 1 << 0;
    pub const NO_RESIZE: i32 = 1 << 1;
    pub const NO_MOVE: i32 = 1 << 2;
    pub const NO_SCROLLBAR: i32 = 1 << 3;
    pub const NO_SCROLL_WITH_MOUSE: i32 = 1 << 4;
    pub const NO_COLLAPSE: i32 = 1 << 5;
    pub const ALWAYS_AUTO_RESIZE: i32 = 1 << 6;
    pub const NO_BACKGROUND: i32 = 1 << 7;
    pub const NO_SAVED_SETTINGS: i32 = 1 << 8;
    pub const NO_MOUSE_INPUTS: i32 = 1 << 9;
    pub const MENU_BAR: i32 = 1 << 10;
    pub const HORIZONTAL_SCROLLBAR: i32 = 1 << 11;
    pub const NO_FOCUS_ON_APPEARING: i32 = 1 << 12;
    pub const NO_BRING_TO_FRONT_ON_FOCUS: i32 = 1 << 13;
    pub const ALWAYS_VERTICAL_SCROLLBAR: i32 = 1 << 14;
    pub const ALWAYS_HORIZONTAL_SCROLLBAR: i32 = 1 << 15;
    pub const NO_NAV_INPUTS: i32 = 1 << 16;
    pub const NO_NAV_FOCUS: i32 = 1 << 17;
    pub const UNSAVED_DOCUMENT: i32 = 1 << 18;
    pub const NO_NAV: i32 = NO_NAV_INPUTS | NO_NAV_FOCUS;
    pub const NO_DECORATION: i32 = NO_TITLE_BAR | NO_RESIZE | NO_SCROLLBAR | NO_COLLAPSE;
    pub const NO_INPUTS: i32 = NO_MOUSE_INPUTS | NO_NAV_INPUTS | NO_NAV_FOCUS;
}

/// IO config flags for `ImGui::set_config_flags()` (mirrors `ImGuiConfigFlags_*`)
pub mod config_flags {
    pub const NONE: i32 = 0;
    /// Enable keyboard navigation: Tab, arrows, Space/Enter to activate.
    pub const NAV_ENABLE_KEYBOARD: i32 = 1 << 0;
    /// Enable gamepad navigation (backend must set `HasGamepad`).
    pub const NAV_ENABLE_GAMEPAD: i32 = 1 << 1;
    /// Instruct Dear ImGui to disable mouse inputs and interactions.
    pub const NO_MOUSE: i32 = 1 << 4;
    /// Request the backend to not alter mouse cursor shape and visibility.
    pub const NO_MOUSE_CURSOR_CHANGE: i32 = 1 << 5;
}

/// Condition flags for `set_next_window_pos()` and `set_next_window_size()`
pub mod cond {
    pub const NONE: i32 = 0;
    pub const ALWAYS: i32 = 1 << 0;
    pub const ONCE: i32 = 1 << 1;
    pub const FIRST_USE_EVER: i32 = 1 << 2;
    pub const APPEARING: i32 = 1 << 3;
}

/// Table flags for `begin_table()`
pub mod table_flags {
    pub const NONE: i32 = 0;
    pub const RESIZABLE: i32 = 1 << 0;
    pub const REORDERABLE: i32 = 1 << 1;
    pub const HIDEABLE: i32 = 1 << 2;
    pub const SORTABLE: i32 = 1 << 3;
    pub const NO_SAVED_SETTINGS: i32 = 1 << 4;
    pub const CONTEXT_MENU_IN_BODY: i32 = 1 << 5;
    pub const ROW_BG: i32 = 1 << 6;
    pub const BORDERS_INNER_H: i32 = 1 << 7;
    pub const BORDERS_OUTER_H: i32 = 1 << 8;
    pub const BORDERS_INNER_V: i32 = 1 << 9;
    pub const BORDERS_OUTER_V: i32 = 1 << 10;
    pub const BORDERS_H: i32 = BORDERS_INNER_H | BORDERS_OUTER_H;
    pub const BORDERS_V: i32 = BORDERS_INNER_V | BORDERS_OUTER_V;
    pub const BORDERS_INNER: i32 = BORDERS_INNER_V | BORDERS_INNER_H;
    pub const BORDERS_OUTER: i32 = BORDERS_OUTER_V | BORDERS_OUTER_H;
    pub const BORDERS: i32 = BORDERS_INNER | BORDERS_OUTER;
}

/// Style color indices for `push_style_color()`
pub mod col {
    pub const TEXT: i32 = 0;
    pub const TEXT_DISABLED: i32 = 1;
    pub const WINDOW_BG: i32 = 2;
    pub const CHILD_BG: i32 = 3;
    pub const POPUP_BG: i32 = 4;
    pub const BORDER: i32 = 5;
    pub const BORDER_SHADOW: i32 = 6;
    pub const FRAME_BG: i32 = 7;
    pub const FRAME_BG_HOVERED: i32 = 8;
    pub const FRAME_BG_ACTIVE: i32 = 9;
    pub const TITLE_BG: i32 = 10;
    pub const TITLE_BG_ACTIVE: i32 = 11;
    pub const TITLE_BG_COLLAPSED: i32 = 12;
    pub const MENU_BAR_BG: i32 = 13;
    pub const SCROLLBAR_BG: i32 = 14;
    pub const SCROLLBAR_GRAB: i32 = 15;
    pub const SCROLLBAR_GRAB_HOVERED: i32 = 16;
    pub const SCROLLBAR_GRAB_ACTIVE: i32 = 17;
    pub const CHECK_MARK: i32 = 18;
    pub const SLIDER_GRAB: i32 = 19;
    pub const SLIDER_GRAB_ACTIVE: i32 = 20;
    pub const BUTTON: i32 = 21;
    pub const BUTTON_HOVERED: i32 = 22;
    pub const BUTTON_ACTIVE: i32 = 23;
    pub const HEADER: i32 = 24;
    pub const HEADER_HOVERED: i32 = 25;
    pub const HEADER_ACTIVE: i32 = 26;
}

/// Dear ImGui context and safe wrapper
pub struct ImGui {
    ctx: *mut std::ffi::c_void,
    window: *const GLFWwindow,
}

impl ImGui {
    /// Create a new ImGui context and initialize backends for the given GLFW window.
    ///
    /// On Windows, this automatically applies DPI scaling to handle high-DPI displays.
    ///
    /// # Arguments
    /// * `window` - Raw GLFW window pointer from `Window::glfw_window_ptr()`
    /// * `install_callbacks` - If true, ImGui will install its own GLFW callbacks
    ///
    /// # Safety
    /// The window pointer must be valid for the lifetime of the ImGui context.
    pub fn new(window: *const GLFWwindow, install_callbacks: bool) -> Self {
        let ctx = unsafe { ffi::imgui_create_context() };

        let glsl_version = CString::new("#version 330").unwrap();
        unsafe {
            ffi::imgui_init_for_glfw(window, if install_callbacks { 1 } else { 0 });
            ffi::imgui_init_for_opengl3(glsl_version.as_ptr());

            // Apply DPI scaling on Windows
            #[cfg(target_os = "windows")]
            ffi::imgui_apply_dpi_scale(window);
        }

        Self { ctx, window }
    }

    /// Get the DPI scale factor for the window.
    ///
    /// Returns the content scale reported by GLFW. On Windows with 150% display scaling,
    /// this returns 1.5. On standard DPI displays, this returns 1.0.
    pub fn get_dpi_scale(&self) -> f32 {
        unsafe { ffi::imgui_get_dpi_scale(self.window) }
    }

    /// Manually apply DPI scaling.
    ///
    /// This scales the ImGui style sizes and rebuilds the font atlas with a scaled font.
    /// Called automatically on Windows during construction, but can be called manually
    /// if DPI changes at runtime.
    pub fn apply_dpi_scale(&self) {
        unsafe { ffi::imgui_apply_dpi_scale(self.window) }
    }

    /// Start a new ImGui frame. Call this at the beginning of your render loop.
    pub fn new_frame(&self) {
        unsafe { ffi::imgui_new_frame() };
    }

    /// Finalize and render the ImGui frame. Call this at the end of your render loop.
    pub fn render(&self) {
        unsafe {
            ffi::imgui_render();
            ffi::imgui_opengl3_render_draw_data();
        }
    }

    /// Returns true if ImGui wants to capture mouse input (e.g., mouse is over an ImGui window).
    pub fn want_capture_mouse(&self) -> bool {
        unsafe { ffi::imgui_io_want_capture_mouse() != 0 }
    }

    /// Returns true if ImGui wants to capture keyboard input.
    pub fn want_capture_keyboard(&self) -> bool {
        unsafe { ffi::imgui_io_want_capture_keyboard() != 0 }
    }

    /// OR the given bitmask into `io.ConfigFlags`. Use the constants in
    /// [`config_flags`] — e.g. `NAV_ENABLE_KEYBOARD` to turn on Dear ImGui's
    /// built-in keyboard navigation (arrows + Space/Enter inside focused windows).
    pub fn set_config_flags(&self, flags: i32) {
        unsafe { ffi::imgui_io_set_config_flags(flags as std::os::raw::c_int) }
    }

    // ---- Windows ----

    /// Begin a new window. Returns true if the window is not collapsed.
    ///
    /// # Arguments
    /// * `name` - Window title/ID
    /// * `open` - Optional mutable bool; if Some, shows a close button
    /// * `flags` - Window flags from `window_flags` module
    pub fn begin(&self, name: &str, open: Option<&mut bool>, flags: i32) -> bool {
        let name_c = CString::new(name).unwrap();
        unsafe {
            match open {
                Some(open_ref) => {
                    let mut open_int = if *open_ref { 1 } else { 0 };
                    let result = ffi::imgui_begin(name_c.as_ptr(), &mut open_int, flags);
                    *open_ref = open_int != 0;
                    result != 0
                }
                None => ffi::imgui_begin(name_c.as_ptr(), ptr::null_mut(), flags) != 0,
            }
        }
    }

    /// End the current window. Must be called after `begin()`.
    pub fn end(&self) {
        unsafe { ffi::imgui_end() };
    }

    /// Set position of the next window.
    pub fn set_next_window_pos(&self, x: f32, y: f32, cond: i32) {
        unsafe { ffi::imgui_set_next_window_pos(x, y, cond) };
    }

    /// Set size of the next window.
    pub fn set_next_window_size(&self, width: f32, height: f32, cond: i32) {
        unsafe { ffi::imgui_set_next_window_size(width, height, cond) };
    }

    // ---- Widgets: Text ----

    /// Display text.
    pub fn text(&self, text: &str) {
        let text_c = CString::new(text).unwrap();
        unsafe { ffi::imgui_text(text_c.as_ptr()) };
    }

    // ---- Widgets: Buttons ----

    /// Button widget. Returns true if clicked.
    pub fn button(&self, label: &str) -> bool {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_button(label_c.as_ptr()) != 0 }
    }

    /// Checkbox widget. Returns true if value changed.
    pub fn checkbox(&self, label: &str, v: &mut bool) -> bool {
        let label_c = CString::new(label).unwrap();
        let mut v_int = if *v { 1 } else { 0 };
        let changed = unsafe { ffi::imgui_checkbox(label_c.as_ptr(), &mut v_int) != 0 };
        *v = v_int != 0;
        changed
    }

    // ---- Widgets: Sliders ----

    /// Float slider. Returns true if value changed.
    pub fn slider_float(&self, label: &str, v: &mut f32, min: f32, max: f32) -> bool {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_slider_float(label_c.as_ptr(), v, min, max) != 0 }
    }

    /// Int slider. Returns true if value changed.
    pub fn slider_int(&self, label: &str, v: &mut i32, min: i32, max: i32) -> bool {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_slider_int(label_c.as_ptr(), v, min, max) != 0 }
    }

    // ---- Widgets: Input ----

    /// Float input. Returns true if value changed.
    pub fn input_float(&self, label: &str, v: &mut f32) -> bool {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_input_float(label_c.as_ptr(), v) != 0 }
    }

    /// Int input. Returns true if value changed.
    pub fn input_int(&self, label: &str, v: &mut i32) -> bool {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_input_int(label_c.as_ptr(), v) != 0 }
    }

    /// Single-line text input bound to a `String`. Returns true on change.
    /// The input is capped at 255 bytes; longer existing values are truncated.
    pub fn input_text(&self, label: &str, value: &mut String) -> bool {
        const BUF_LEN: usize = 256;
        let mut buf = [0u8; BUF_LEN];
        let bytes = value.as_bytes();
        let n = bytes.len().min(BUF_LEN - 1);
        buf[..n].copy_from_slice(&bytes[..n]);

        let label_c = CString::new(label).unwrap();
        let changed = unsafe {
            ffi::imgui_input_text(
                label_c.as_ptr(),
                buf.as_mut_ptr() as *mut std::os::raw::c_char,
                BUF_LEN as std::os::raw::c_ulong,
                0,
            ) != 0
        };
        if changed {
            let new_len = buf.iter().position(|&b| b == 0).unwrap_or(BUF_LEN);
            *value = String::from_utf8_lossy(&buf[..new_len]).into_owned();
        }
        changed
    }

    // ---- Widgets: Color ----

    /// RGB color editor. Returns true if value changed.
    pub fn color_edit3(&self, label: &str, col: &mut [f32; 3]) -> bool {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_color_edit3(label_c.as_ptr(), col.as_mut_ptr()) != 0 }
    }

    /// RGBA color editor. Returns true if value changed.
    pub fn color_edit4(&self, label: &str, col: &mut [f32; 4]) -> bool {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_color_edit4(label_c.as_ptr(), col.as_mut_ptr()) != 0 }
    }

    // ---- Layout ----

    /// Place next widget on the same line as the previous one.
    pub fn same_line(&self) {
        unsafe { ffi::imgui_same_line() };
    }

    /// Add a horizontal separator with a centered text label (the line is
    /// drawn on either side of the text). Useful for titling sections in
    /// an options window.
    pub fn separator_text(&self, label: &str) {
        let c = CString::new(label).unwrap();
        unsafe { ffi::imgui_separator_text(c.as_ptr()) };
    }

    /// Add a horizontal separator.
    pub fn separator(&self) {
        unsafe { ffi::imgui_separator() };
    }

    /// Add vertical spacing.
    pub fn spacing(&self) {
        unsafe { ffi::imgui_spacing() };
    }

    /// Add a dummy item of given size.
    pub fn dummy(&self, width: f32, height: f32) {
        unsafe { ffi::imgui_dummy(width, height) };
    }

    /// Indent content.
    pub fn indent(&self, indent_w: f32) {
        unsafe { ffi::imgui_indent(indent_w) };
    }

    /// Unindent content.
    pub fn unindent(&self, indent_w: f32) {
        unsafe { ffi::imgui_unindent(indent_w) };
    }

    // ---- Tree ----

    /// Begin a tree node. Returns true if the node is open.
    pub fn tree_node(&self, label: &str) -> bool {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_tree_node(label_c.as_ptr()) != 0 }
    }

    /// End a tree node. Call only if `tree_node()` returned true.
    pub fn tree_pop(&self) {
        unsafe { ffi::imgui_tree_pop() };
    }

    // ---- Combo ----

    /// Begin a combo box. Returns true if the combo is open.
    pub fn begin_combo(&self, label: &str, preview: &str, flags: i32) -> bool {
        let label_c = CString::new(label).unwrap();
        let preview_c = CString::new(preview).unwrap();
        unsafe { ffi::imgui_begin_combo(label_c.as_ptr(), preview_c.as_ptr(), flags) != 0 }
    }

    /// End a combo box. Call only if `begin_combo()` returned true.
    pub fn end_combo(&self) {
        unsafe { ffi::imgui_end_combo() };
    }

    /// Selectable item in a combo/list. Returns true if clicked.
    pub fn selectable(&self, label: &str, selected: bool, flags: i32) -> bool {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_selectable(label_c.as_ptr(), if selected { 1 } else { 0 }, flags) != 0 }
    }

    // ---- Menu ----

    /// Begin the main menu bar.
    pub fn begin_main_menu_bar(&self) -> bool {
        unsafe { ffi::imgui_begin_main_menu_bar() != 0 }
    }

    /// End the main menu bar.
    pub fn end_main_menu_bar(&self) {
        unsafe { ffi::imgui_end_main_menu_bar() };
    }

    /// Begin a menu.
    pub fn begin_menu(&self, label: &str, enabled: bool) -> bool {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_begin_menu(label_c.as_ptr(), if enabled { 1 } else { 0 }) != 0 }
    }

    /// End a menu.
    pub fn end_menu(&self) {
        unsafe { ffi::imgui_end_menu() };
    }

    /// Menu item. Returns true if activated.
    pub fn menu_item(&self, label: &str, shortcut: Option<&str>, selected: bool, enabled: bool) -> bool {
        let label_c = CString::new(label).unwrap();
        let shortcut_c = shortcut.map(|s| CString::new(s).unwrap());
        let shortcut_ptr = shortcut_c.as_ref().map_or(ptr::null(), |s| s.as_ptr());
        unsafe {
            ffi::imgui_menu_item(
                label_c.as_ptr(),
                shortcut_ptr,
                if selected { 1 } else { 0 },
                if enabled { 1 } else { 0 },
            ) != 0
        }
    }

    // ---- Tooltips ----

    /// Set a tooltip for the previous item.
    pub fn set_tooltip(&self, text: &str) {
        let text_c = CString::new(text).unwrap();
        unsafe { ffi::imgui_set_tooltip(text_c.as_ptr()) };
    }

    /// Begin a tooltip.
    pub fn begin_tooltip(&self) -> bool {
        unsafe { ffi::imgui_begin_tooltip() != 0 }
    }

    /// End a tooltip.
    pub fn end_tooltip(&self) {
        unsafe { ffi::imgui_end_tooltip() };
    }

    // ---- Popups ----

    /// Begin a popup.
    pub fn begin_popup(&self, str_id: &str, flags: i32) -> bool {
        let str_id_c = CString::new(str_id).unwrap();
        unsafe { ffi::imgui_begin_popup(str_id_c.as_ptr(), flags) != 0 }
    }

    /// Begin a modal popup.
    pub fn begin_popup_modal(&self, name: &str, open: Option<&mut bool>, flags: i32) -> bool {
        let name_c = CString::new(name).unwrap();
        unsafe {
            match open {
                Some(open_ref) => {
                    let mut open_int = if *open_ref { 1 } else { 0 };
                    let result = ffi::imgui_begin_popup_modal(name_c.as_ptr(), &mut open_int, flags);
                    *open_ref = open_int != 0;
                    result != 0
                }
                None => ffi::imgui_begin_popup_modal(name_c.as_ptr(), ptr::null_mut(), flags) != 0,
            }
        }
    }

    /// End a popup.
    pub fn end_popup(&self) {
        unsafe { ffi::imgui_end_popup() };
    }

    /// Open a popup.
    pub fn open_popup(&self, str_id: &str) {
        let str_id_c = CString::new(str_id).unwrap();
        unsafe { ffi::imgui_open_popup(str_id_c.as_ptr()) };
    }

    /// Close the current popup.
    pub fn close_current_popup(&self) {
        unsafe { ffi::imgui_close_current_popup() };
    }

    // ---- Tables ----

    /// Begin a table.
    pub fn begin_table(&self, str_id: &str, columns: i32, flags: i32) -> bool {
        let str_id_c = CString::new(str_id).unwrap();
        unsafe { ffi::imgui_begin_table(str_id_c.as_ptr(), columns, flags) != 0 }
    }

    /// End a table.
    pub fn end_table(&self) {
        unsafe { ffi::imgui_end_table() };
    }

    /// Move to the next row in a table.
    pub fn table_next_row(&self) {
        unsafe { ffi::imgui_table_next_row() };
    }

    /// Move to the next column in a table.
    pub fn table_next_column(&self) -> bool {
        unsafe { ffi::imgui_table_next_column() != 0 }
    }

    /// Set the current column index.
    pub fn table_set_column_index(&self, column: i32) -> bool {
        unsafe { ffi::imgui_table_set_column_index(column) != 0 }
    }

    /// Setup a column (call before first row).
    pub fn table_setup_column(&self, label: &str, flags: i32, init_width: f32) {
        let label_c = CString::new(label).unwrap();
        unsafe { ffi::imgui_table_setup_column(label_c.as_ptr(), flags, init_width) };
    }

    /// Display column headers row.
    pub fn table_headers_row(&self) {
        unsafe { ffi::imgui_table_headers_row() };
    }

    // ---- Style ----

    /// Push a style color.
    pub fn push_style_color(&self, idx: i32, r: f32, g: f32, b: f32, a: f32) {
        unsafe { ffi::imgui_push_style_color(idx, r, g, b, a) };
    }

    /// Pop style colors.
    pub fn pop_style_color(&self, count: i32) {
        unsafe { ffi::imgui_pop_style_color(count) };
    }

    // ---- ID Stack ----

    /// Push an integer ID.
    pub fn push_id_int(&self, id: i32) {
        unsafe { ffi::imgui_push_id_int(id) };
    }

    /// Push a string ID.
    pub fn push_id(&self, id: &str) {
        let id_c = CString::new(id).unwrap();
        unsafe { ffi::imgui_push_id_str(id_c.as_ptr()) };
    }

    /// Pop an ID.
    pub fn pop_id(&self) {
        unsafe { ffi::imgui_pop_id() };
    }

    // ---- Item Queries ----

    /// Returns true if the last item is hovered.
    pub fn is_item_hovered(&self) -> bool {
        unsafe { ffi::imgui_is_item_hovered() != 0 }
    }

    /// Returns true if the last item was clicked.
    pub fn is_item_clicked(&self, mouse_button: i32) -> bool {
        unsafe { ffi::imgui_is_item_clicked(mouse_button) != 0 }
    }

    /// Returns true if the last item is active.
    pub fn is_item_active(&self) -> bool {
        unsafe { ffi::imgui_is_item_active() != 0 }
    }

    // ---- Demo ----

    /// Show the ImGui demo window.
    pub fn show_demo_window(&self, open: Option<&mut bool>) {
        unsafe {
            match open {
                Some(open_ref) => {
                    let mut open_int = if *open_ref { 1 } else { 0 };
                    ffi::imgui_show_demo_window(&mut open_int);
                    *open_ref = open_int != 0;
                }
                None => ffi::imgui_show_demo_window(ptr::null_mut()),
            }
        }
    }
}

impl Drop for ImGui {
    fn drop(&mut self) {
        unsafe {
            ffi::imgui_shutdown_opengl3();
            ffi::imgui_shutdown_glfw();
            ffi::imgui_destroy_context(self.ctx);
        }
    }
}

// Note: ImGui is !Send and !Sync because it contains a raw pointer (*mut c_void)