diff --git a/README.markdown b/README.markdown index ad774f3..4cbc5bb 100644 --- a/README.markdown +++ b/README.markdown @@ -10,9 +10,9 @@ ![Hello world](hello_world.png) ```rust -Window::new("Hello world") +ui.window("Hello world") .size([300.0, 100.0], Condition::FirstUseEver) - .build(&ui, || { + .build(|| { ui.text("Hello world!"); ui.text("こんにちは世界!"); ui.text("This...is...imgui-rs!"); diff --git a/imgui-examples/examples/collapsing_header.rs b/imgui-examples/examples/collapsing_header.rs index 74873d7..d7fc86f 100644 --- a/imgui-examples/examples/collapsing_header.rs +++ b/imgui-examples/examples/collapsing_header.rs @@ -8,11 +8,12 @@ fn main() { }; let system = support::init(file!()); system.main_loop(move |run, ui| { - let w = Window::new("Collapsing header") + let w = ui + .window("Collapsing header") .opened(run) .position([20.0, 20.0], Condition::Appearing) .size([700.0, 500.0], Condition::Appearing); - w.build(ui, || { + w.build(|| { if CollapsingHeader::new("I'm a collapsing header. Click me!").build(ui) { ui.text( "A collapsing header can be used to toggle rendering of a group of widgets", diff --git a/imgui-examples/examples/color_button.rs b/imgui-examples/examples/color_button.rs index 5c19746..b8f0484 100644 --- a/imgui-examples/examples/color_button.rs +++ b/imgui-examples/examples/color_button.rs @@ -17,12 +17,13 @@ fn main() { } fn example_selector(run: &mut bool, ui: &mut Ui, state: &mut State) { - let w = Window::new("Color button examples") + let w = ui + .window("Color button examples") .opened(run) .position([20.0, 20.0], Condition::Appearing) .size([700.0, 100.0], Condition::Appearing) .resizable(false); - w.build(ui, || { + w.build(|| { let ex1 = ui.radio_button("Example 1: Basics", &mut state.example, 1); let ex2 = ui.radio_button("Example 2: Alpha component", &mut state.example, 2); let ex3 = ui.radio_button("Example 3: Input format", &mut state.example, 3); @@ -33,10 +34,11 @@ fn example_selector(run: &mut bool, ui: &mut Ui, state: &mut State) { } fn example_1(ui: &Ui, state: &mut State) { - let w = Window::new("Example 1: Basics") + let w = ui + .window("Example 1: Basics") .size([700.0, 300.0], Condition::Appearing) .position([20.0, 140.0], Condition::Appearing); - w.build(ui, || { + w.build(|| { ui.text_wrapped( "Color button is a widget that displays a color value as a clickable rectangle. \ It also supports a tooltip with detailed information about the color value. \ @@ -73,10 +75,11 @@ fn example_1(ui: &Ui, state: &mut State) { } fn example_2(ui: &Ui) { - let w = Window::new("Example 2: Alpha component") + let w = ui + .window("Example 2: Alpha component") .size([700.0, 320.0], Condition::Appearing) .position([20.0, 140.0], Condition::Appearing); - w.build(ui, || { + w.build(|| { ui.text_wrapped( "The displayed color is passed to the button as four float values between \ 0.0 - 1.0 (RGBA). If you don't care about the alpha component, it can be \ @@ -123,10 +126,11 @@ fn example_2(ui: &Ui) { } fn example_3(ui: &Ui) { - let w = Window::new("Example 3: Input format") + let w = ui + .window("Example 3: Input format") .size([700.0, 320.0], Condition::Appearing) .position([20.0, 140.0], Condition::Appearing); - w.build(ui, || { + w.build(|| { ui.text("This button interprets the input value [1.0, 0.0, 0.0, 1.0] as RGB(A) (default):"); ColorButton::new("RGBA red", [1.0, 0.0, 0.0, 1.0]).build(ui); diff --git a/imgui-examples/examples/creating_windows.rs b/imgui-examples/examples/creating_windows.rs new file mode 100644 index 0000000..7f58f2d --- /dev/null +++ b/imgui-examples/examples/creating_windows.rs @@ -0,0 +1,67 @@ +mod support; + +fn main() { + let system = support::init(file!()); + + system.main_loop(move |_, ui| { + // If we don't explicitly create a window before creating some kind of widget, then Dear Imgui will automatically create one + ui.text("This text will appear in a default window titled 'Debug'"); + + // However, in almost all cases it's best to make a window, so it has a useful title etc + + // imgui-rs has two main methods of creating windows (and these same approaches + // apply to many other widgets). First, callback based: + + ui.window("My window via callback").build(|| { + ui.text("This content appears in a window"); + + // Everything in this callback appears in the window, like this button: + ui.button("This button"); + }); + + // Often the callback approach is most convenient, however occasionally the callbacks can be hard to use. + // In this case, there is the "token based" approach. You call a method and get a "window token", + // everything that happens until the token is dropped is included in the window this is more-or-less how + // the Dear ImGui C++ API works) + + // Here we (maybe) get a window token: + let window_token = ui.window("Token based window").begin(); + if let Some(_t) = window_token { + // If the token is Some(...) then the window contents are visible, so we need to draw them! + ui.text("Window contents!") + } + + // Here we create a window with a specific size, and force it to always have a vertical scrollbar visible + ui.window("Big complex window") + .size([200.0, 100.0], imgui::Condition::FirstUseEver) + .always_vertical_scrollbar(true) + .build(|| { + ui.text("Imagine something complicated here.."); + + // Note you can create windows inside other windows, however, they both appear as separate windows. + // For example, somewhere deep inside a complex window, we can quickly create a widget to display a + // variable, like a graphical "debug print" + ui.window("Confusion") + .build(|| ui.text(format!("Some variable: {:?}", ui.io().mouse_pos))) + }); + + // If you want to nest windows inside other windows, you can a "child window". + // This is essentially a scrollable area, with all the same properties as a regular window + ui.window("Parent window").build(|| { + ui.child_window("Child window") + .size([100.0, 100.0]) + .build(|| { + for _ in 0..10 { + ui.text("Lines and"); + } + }); + ui.child_window("Second child window") + .size([100.0, 100.0]) + .build(|| { + for _ in 0..10 { + ui.text("More and"); + } + }); + }); + }); +} diff --git a/imgui-examples/examples/custom_textures.rs b/imgui-examples/examples/custom_textures.rs index a76b03f..6b8cfc5 100644 --- a/imgui-examples/examples/custom_textures.rs +++ b/imgui-examples/examples/custom_textures.rs @@ -78,9 +78,9 @@ impl CustomTexturesApp { } fn show_textures(&self, ui: &Ui) { - Window::new("Hello textures") + ui.window("Hello textures") .size([400.0, 400.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { ui.text("Hello textures!"); if let Some(my_texture_id) = self.my_texture_id { ui.text("Some generated texture"); diff --git a/imgui-examples/examples/disablement.rs b/imgui-examples/examples/disablement.rs index c5df984..fa7dd7a 100644 --- a/imgui-examples/examples/disablement.rs +++ b/imgui-examples/examples/disablement.rs @@ -1,3 +1,5 @@ +//! Demonstrates disabling widgets. Prevents mouse interaction and greys out widgets + use imgui::*; mod support; @@ -11,9 +13,9 @@ fn main() { let mut click_count = 0; system.main_loop(move |_, ui| { - Window::new("Disabling widgets") + ui.window("Disabling widgets") .size([300.0, 200.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { ui.checkbox("Edit mode", &mut edit_mode); ui.checkbox("Safe mode", &mut safe_mode); diff --git a/imgui-examples/examples/draw_list.rs b/imgui-examples/examples/draw_list.rs index dfd9125..f575e77 100644 --- a/imgui-examples/examples/draw_list.rs +++ b/imgui-examples/examples/draw_list.rs @@ -57,10 +57,10 @@ fn main() { ); } - Window::new("Draw list") + ui.window("Draw list") .size([300.0, 110.0], Condition::FirstUseEver) .scroll_bar(false) - .build(ui, || { + .build(|| { ui.button("random button"); let draw_list = ui.get_window_draw_list(); let o = ui.cursor_screen_pos(); diff --git a/imgui-examples/examples/hello_world.rs b/imgui-examples/examples/hello_world.rs index 56eb774..29f74ed 100644 --- a/imgui-examples/examples/hello_world.rs +++ b/imgui-examples/examples/hello_world.rs @@ -7,9 +7,9 @@ fn main() { let mut value = 0; let choices = ["test test this is 1", "test test this is 2"]; system.main_loop(move |_, ui| { - Window::new("Hello world") + ui.window("Hello world") .size([300.0, 110.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { ui.text_wrapped("Hello world!"); ui.text_wrapped("こんにちは世界!"); if ui.button(choices[value]) { diff --git a/imgui-examples/examples/id_wrangling.rs b/imgui-examples/examples/id_wrangling.rs new file mode 100644 index 0000000..fbd0ded --- /dev/null +++ b/imgui-examples/examples/id_wrangling.rs @@ -0,0 +1,45 @@ +mod support; + +fn main() { + let system = support::init(file!()); + system.main_loop(move |_, ui| { + let items = vec!["a", "b", "c", "d"]; + + ui.window("Broken Example") + .position([0.0, 0.0], imgui::Condition::FirstUseEver) + .size([390.0, 200.0], imgui::Condition::FirstUseEver) + .build(|| { + ui.text("Broken! Only first button responds to clicks"); + + // Because all our buttons have the same label (and thus ID), + // only the first button responds to clicks! + for it in &items { + ui.text(it); + for num in 0..5 { + ui.same_line(); + if ui.button("Example") { + println!("{}: {}", it, num); + } + } + } + }); + + ui.window("Good Example") + .position([400.0, 0.0], imgui::Condition::FirstUseEver) + .size([390.0, 200.0], imgui::Condition::FirstUseEver) + .build(|| { + ui.text("Works!"); + for it in &items { + let _label_id = ui.push_id(it); + ui.text(it); + for num in 0..5 { + let _num_id = ui.push_id(num); + ui.same_line(); + if ui.button("Example") { + println!("{}: {}", it, num); + } + } + } + }); + }); +} diff --git a/imgui-examples/examples/keyboard.rs b/imgui-examples/examples/keyboard.rs index d2d9c3f..a6aaa05 100644 --- a/imgui-examples/examples/keyboard.rs +++ b/imgui-examples/examples/keyboard.rs @@ -14,9 +14,9 @@ fn main() { let mut text_buffer = String::new(); system.main_loop(move |_, ui| { - Window::new("Means of accessing key state") + ui.window("Means of accessing key state") .size([500.0, 300.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { // You can check if a key is currently held down if ui.is_key_down(Key::A) { ui.text("The A key is down!"); diff --git a/imgui-examples/examples/long_list.rs b/imgui-examples/examples/long_list.rs index 8a745e6..2204c53 100644 --- a/imgui-examples/examples/long_list.rs +++ b/imgui-examples/examples/long_list.rs @@ -15,9 +15,9 @@ fn main() { let system = support::init(file!()); system.main_loop(move |_, ui| { - Window::new("Hello long world") + ui.window("Hello long world") .size([300.0, 110.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { let mut clipper = imgui::ListClipper::new(lots_of_words.len() as i32) .items_height(ui.current_font_size()) .begin(ui); diff --git a/imgui-examples/examples/multiple_fonts.rs b/imgui-examples/examples/multiple_fonts.rs index 5e0e927..4043c51 100644 --- a/imgui-examples/examples/multiple_fonts.rs +++ b/imgui-examples/examples/multiple_fonts.rs @@ -19,7 +19,7 @@ fn main() { .reload_font_texture(&mut system.imgui) .expect("Failed to reload fonts"); system.main_loop(move |run, ui| { - Window::new("Hello world").opened(run).build(ui, || { + ui.window("Hello world").opened(run).build(|| { ui.text("Hello, I'm the default font!"); let _roboto = ui.push_font(roboto); ui.text("Hello, I'm Roboto Regular!"); diff --git a/imgui-examples/examples/progress_bar.rs b/imgui-examples/examples/progress_bar.rs index 9d11746..81011d8 100644 --- a/imgui-examples/examples/progress_bar.rs +++ b/imgui-examples/examples/progress_bar.rs @@ -5,11 +5,12 @@ mod support; fn main() { let system = support::init(file!()); system.main_loop(move |run, ui| { - let w = Window::new("Progress bar") + let w = ui + .window("Progress bar") .opened(run) .position([20.0, 20.0], Condition::Appearing) .size([700.0, 200.0], Condition::Appearing); - w.build(ui, || { + w.build(|| { ui.text("This is a simple progress bar:"); ProgressBar::new(0.5).build(ui); diff --git a/imgui-examples/examples/radio_button.rs b/imgui-examples/examples/radio_button.rs index cb86a82..20859b4 100644 --- a/imgui-examples/examples/radio_button.rs +++ b/imgui-examples/examples/radio_button.rs @@ -16,12 +16,13 @@ fn main() { } fn example_selector(run: &mut bool, ui: &mut Ui, state: &mut State) { - let w = Window::new("Radio button examples") + let w = ui + .window("Radio button examples") .opened(run) .position([20.0, 20.0], Condition::Appearing) .size([700.0, 80.0], Condition::Appearing) .resizable(false); - w.build(ui, || { + w.build(|| { let mut clicked = false; clicked |= ui.radio_button("Example 1: Boolean radio buttons", &mut state.example, 1); clicked |= ui.radio_button("Example 2: Radio buttons", &mut state.example, 2); @@ -32,10 +33,11 @@ fn example_selector(run: &mut bool, ui: &mut Ui, state: &mut State) { } fn example_1(ui: &Ui, state: &mut State) { - let w = Window::new("Example 1: Boolean radio buttons") + let w = ui + .window("Example 1: Boolean radio buttons") .size([700.0, 200.0], Condition::Appearing) .position([20.0, 120.0], Condition::Appearing); - w.build(ui, || { + w.build(|| { ui.text_wrapped( "Boolean radio buttons accept a boolean active state, which is passed as a value and \ not as a mutable reference. This means that it's not updated automatically, so you \ @@ -58,10 +60,11 @@ fn example_1(ui: &Ui, state: &mut State) { } fn example_2(ui: &Ui, state: &mut State) { - let w = Window::new("Example 2: Radio buttons") + let w = ui + .window("Example 2: Radio buttons") .size([700.0, 300.0], Condition::Appearing) .position([20.0, 120.0], Condition::Appearing); - w.build(ui, || { + w.build(|| { ui.text_wrapped( "Normal radio buttons accept a mutable reference to state, and the value \ corresponding to this button. They are very flexible, because the value can be any \ diff --git a/imgui-examples/examples/slider.rs b/imgui-examples/examples/slider.rs index e5e5bcc..07b4072 100644 --- a/imgui-examples/examples/slider.rs +++ b/imgui-examples/examples/slider.rs @@ -16,12 +16,13 @@ fn main() { } fn example_selector(run: &mut bool, ui: &mut Ui, state: &mut State) { - let w = Window::new("Slider examples") + let w = ui + .window("Slider examples") .opened(run) .position([20.0, 20.0], Condition::Appearing) .size([700.0, 80.0], Condition::Appearing) .resizable(false); - w.build(ui, || { + w.build(|| { let mut clicked = false; clicked |= ui.radio_button("Example 1: Basic sliders", &mut state.example, 1); clicked |= ui.radio_button("Example 2: Slider arrays", &mut state.example, 2); @@ -32,10 +33,11 @@ fn example_selector(run: &mut bool, ui: &mut Ui, state: &mut State) { } fn example_1(ui: &Ui, state: &mut State) { - let w = Window::new("Example 1: Basic sliders") + let w = ui + .window("Example 1: Basic sliders") .size([700.0, 340.0], Condition::Appearing) .position([20.0, 120.0], Condition::Appearing); - w.build(ui, || { + w.build(|| { ui.text("All of the following data types are supported:"); ui.text("Signed: i8 i16 i32 i64"); ui.text("Unsigned: u8 u16 u32 u64"); @@ -67,10 +69,11 @@ fn example_1(ui: &Ui, state: &mut State) { } fn example_2(ui: &Ui, state: &mut State) { - let w = Window::new("Example 2: Slider arrays") + let w = ui + .window("Example 2: Slider arrays") .size([700.0, 260.0], Condition::Appearing) .position([20.0, 120.0], Condition::Appearing); - w.build(ui, || { + w.build(|| { ui.text("You can easily build a slider group from an array of values:"); Slider::new("[u8; 4]", 0, u8::MAX).build_array(ui, &mut state.array); diff --git a/imgui-examples/examples/tables_api.rs b/imgui-examples/examples/tables_api.rs index ae5c63e..47a1d7d 100644 --- a/imgui-examples/examples/tables_api.rs +++ b/imgui-examples/examples/tables_api.rs @@ -29,9 +29,9 @@ fn main() { | TableFlags::NO_BORDERS_IN_BODY; system.main_loop(move |_, ui| { - Window::new("Input text callbacks") + ui.window("Input text callbacks") .size([800.0, 400.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { if let Some(_t) = ui.begin_table("Basic-Table", 3) { // we must also call `next_row` here, because we declined // to set up header rows. If we set up header rows ourselves, diff --git a/imgui-examples/examples/test_window_impl.rs b/imgui-examples/examples/test_window_impl.rs index f47c4a2..f08300d 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -286,15 +286,15 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { ui.show_metrics_window(&mut state.show_app_metrics); } if state.show_app_style_editor { - Window::new("Style Editor") + ui.window("Style Editor") .opened(&mut state.show_app_style_editor) - .build(ui, || ui.show_default_style_editor()); + .build(|| ui.show_default_style_editor()); } if state.show_app_about { - Window::new("About ImGui") + ui.window("About ImGui") .always_auto_resize(true) .opened(&mut state.show_app_about) - .build(ui, || { + .build(|| { ui.text(format!("dear imgui, {}", imgui::dear_imgui_version())); ui.separator(); ui.text("By Omar Cornut and all github contributors."); @@ -317,7 +317,8 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { show_app_log(ui, &mut state.app_log); } - let mut window = Window::new("ImGui Demo") + let mut window = ui + .window("ImGui Demo") .title_bar(!state.no_titlebar) .resizable(!state.no_resize) .movable(!state.no_move) @@ -328,7 +329,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { if !state.no_close { window = window.opened(opened) } - window.build(ui, || { + window.build(|| { ui.push_item_width(-140.0); ui.text(format!("dear imgui says hello. ({})", imgui::dear_imgui_version())); if let Some(menu_bar) = ui.begin_menu_bar() { @@ -872,10 +873,10 @@ fn show_example_menu_file<'a>(ui: &Ui<'a>, state: &mut FileMenuState) { ui.separator(); if let Some(menu) = ui.begin_menu("Options") { MenuItem::new("Enabled").build_with_ref(ui, &mut state.enabled); - ChildWindow::new("child") + ui.child_window("child") .size([0.0, 60.0]) .border(true) - .build(ui, || { + .build(|| { for i in 0..10 { ui.text(format!("Scrolling Text {}", i)); } @@ -900,10 +901,10 @@ fn show_example_menu_file<'a>(ui: &Ui<'a>, state: &mut FileMenuState) { } fn show_example_app_auto_resize(ui: &Ui, state: &mut AutoResizeState, opened: &mut bool) { - Window::new("Example: Auto-resizing window") + ui.window("Example: Auto-resizing window") .opened(opened) .always_auto_resize(true) - .build(ui, || { + .build(|| { ui.text( "Window will resize every-ui to the size of its content. Note that you probably don't want to query the window size to @@ -920,7 +921,7 @@ fn show_example_app_fixed_overlay(ui: &Ui, opened: &mut bool) { const DISTANCE: f32 = 10.0; let window_pos = [DISTANCE, DISTANCE]; let style = ui.push_style_color(StyleColor::WindowBg, [0.0, 0.0, 0.0, 0.3]); - Window::new("Example: Fixed Overlay") + ui.window("Example: Fixed Overlay") .opened(opened) .position(window_pos, Condition::Always) .title_bar(false) @@ -928,7 +929,7 @@ fn show_example_app_fixed_overlay(ui: &Ui, opened: &mut bool) { .always_auto_resize(true) .movable(false) .save_settings(false) - .build(ui, || { + .build(|| { ui.text( "Simple overlay\nin the corner of the screen.\n(right-click to change position)", ); @@ -943,17 +944,17 @@ fn show_example_app_fixed_overlay(ui: &Ui, opened: &mut bool) { } fn show_example_app_manipulating_window_title(ui: &Ui) { - Window::new("Same title as another window##1") + ui.window("Same title as another window##1") .position([100.0, 100.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { ui.text( "This is window 1. My title is the same as window 2, but my identifier is unique.", ); }); - Window::new("Same title as another window##2") + ui.window("Same title as another window##2") .position([100.0, 200.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { ui.text( "This is window 2. My title is the same as window 1, but my identifier is unique.", @@ -963,16 +964,16 @@ My title is the same as window 1, but my identifier is unique.", let ch_idx = (ui.time() / 0.25) as usize & 3; let num = ui.frame_count(); // The C++ version uses rand() here let title = format!("Animated title {} {}###AnimatedTitle", chars[ch_idx], num); - Window::new(title) + ui.window(title) .position([100.0, 300.0], Condition::FirstUseEver) - .build(ui, || ui.text("This window has a changing title")); + .build(|| ui.text("This window has a changing title")); } fn show_example_app_custom_rendering(ui: &Ui, state: &mut CustomRenderingState, opened: &mut bool) { - Window::new("Example: Custom rendering") + ui.window("Example: Custom rendering") .size([350.0, 560.0], Condition::FirstUseEver) .opened(opened) - .build(ui, || { + .build(|| { ui.text("Primitives"); // TODO: Add DragFloat to change value of sz ColorEdit::new("Color", &mut state.col).build(ui); @@ -1217,9 +1218,9 @@ fn show_example_app_custom_rendering(ui: &Ui, state: &mut CustomRenderingState, } fn show_app_log(ui: &Ui, app_log: &mut Vec) { - Window::new("Example: Log") + ui.window("Example: Log") .size([500.0, 400.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { if ui.small_button("[Debug] Add 5 entries") { let categories = ["info", "warn", "error"]; let words = [ @@ -1251,9 +1252,9 @@ fn show_app_log(ui: &Ui, app_log: &mut Vec) { ui.set_clipboard_text(&ImString::from(app_log.join("\n"))); } ui.separator(); - ChildWindow::new("logwindow") + ui.child_window("logwindow") .flags(WindowFlags::HORIZONTAL_SCROLLBAR) - .build(ui, || { + .build(|| { if !app_log.is_empty() { let mut clipper = ListClipper::new(app_log.len() as i32).begin(ui); while clipper.step() { diff --git a/imgui-examples/examples/text_callbacks.rs b/imgui-examples/examples/text_callbacks.rs index 4adcb4a..348ea28 100644 --- a/imgui-examples/examples/text_callbacks.rs +++ b/imgui-examples/examples/text_callbacks.rs @@ -7,9 +7,9 @@ fn main() { let mut buffers = vec![String::default(), String::default(), String::default()]; system.main_loop(move |_, ui| { - Window::new("Input text callbacks") + ui.window("Input text callbacks") .size([500.0, 300.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { ui.text("You can make a variety of buffer callbacks on an Input Text"); ui.text( "or on an InputTextMultiline. In this example, we'll use \ diff --git a/imgui-glow-renderer/examples/04_custom_textures.rs b/imgui-glow-renderer/examples/04_custom_textures.rs index efca79c..70e4d95 100644 --- a/imgui-glow-renderer/examples/04_custom_textures.rs +++ b/imgui-glow-renderer/examples/04_custom_textures.rs @@ -144,9 +144,9 @@ impl TexturesUi { } fn show(&self, ui: &imgui::Ui) { - imgui::Window::new("Hello textures") + ui.window("Hello textures") .size([400.0, 400.0], Condition::FirstUseEver) - .build(ui, || { + .build(|| { ui.text("Hello textures!"); ui.text("Some generated texture"); imgui::Image::new(self.generated_texture, [100.0, 100.0]).build(ui); diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index d453a88..fa7f727 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -314,6 +314,52 @@ impl<'a> Default for Id<'a> { } } +impl<'ui> Ui<'ui> { + /// # Windows + /// Start constructing a window. + /// + /// This, like many objects in the library, uses the builder + /// pattern to set optional arguments (like window size, flags, + /// etc). Once all desired options are set, you must call either + /// [`Window::build`] or [`Window::begin`] must be called to + /// actually create the window. + /// + /// # Examples + /// + /// Create a window using the closure based [`Window::build`]: + /// ```no_run + /// # let mut ctx = imgui::Context::create(); + /// # let ui = ctx.frame(); + /// ui.window("Example Window") + /// .size([100.0, 50.0], imgui::Condition::FirstUseEver) + /// .build(|| { + /// ui.text("An example"); + /// }); + /// ``` + pub fn window>(&'ui self, name: Label) -> Window<'ui, '_, Label> { + Window::new(self, name) + } + + /// Same as [`Ui::window`] but using the "token based" `.begin()` approach. + /// + /// ``` + /// fn example(ui: &imgui::Ui) { + /// let wt = ui.window("Example Window") + /// .size([100.0, 50.0], imgui::Condition::FirstUseEver) + /// .begin(); + /// if wt.is_some() { + /// ui.text("Window is visible"); + /// } + /// // Window ends where where wt is dropped, + /// // or you could call + /// wt.unwrap().end() + /// } + /// ``` + pub fn child_window>(&'ui self, name: Label) -> ChildWindow<'ui, Label> { + ChildWindow::new(self, name) + } +} + // Widgets: Input impl<'ui> Ui<'ui> { #[doc(alias = "InputText", alias = "InputTextWithHint")] @@ -724,15 +770,20 @@ impl<'ui> Ui<'ui> { pub enum Condition { /// Never apply the setting Never = -1, - /// Always apply the setting + + /// Apply the setting every frame Always = sys::ImGuiCond_Always as i8, - /// Apply the setting once per runtime session (only the first call will succeed) + + /// Apply the setting once per runtime session (only the first + /// call will succeed). Will ignore any setting saved in `.ini` Once = sys::ImGuiCond_Once as i8, - /// Apply the setting if the object/window has no persistently saved data (no entry in .ini - /// file) + + /// Apply the setting if the object/window has no persistently + /// saved data (but otherwise use the setting from the .ini file) FirstUseEver = sys::ImGuiCond_FirstUseEver as i8, - /// Apply the setting if the object/window is appearing after being hidden/inactive (or the - /// first time) + + /// Apply the setting if the object/window is appearing after + /// being hidden/inactive (or the first time) Appearing = sys::ImGuiCond_Appearing as i8, } diff --git a/imgui/src/stacks.rs b/imgui/src/stacks.rs index a2037aa..c5964a7 100644 --- a/imgui/src/stacks.rs +++ b/imgui/src/stacks.rs @@ -331,9 +331,81 @@ impl IdStackToken<'_> { /// # ID stack impl<'ui> Ui<'ui> { /// Pushes an identifier to the ID stack. + /// This can be called with an integer, a string, or a pointer. /// /// Returns an `IdStackToken` that can be popped by calling `.end()` /// or by dropping manually. + /// + /// # Examples + /// Dear ImGui uses labels to uniquely identify widgets. For a good explaination, see this part of the [Dear ImGui FAQ][faq] + /// + /// [faq]: https://github.com/ocornut/imgui/blob/v1.84.2/docs/FAQ.md#q-why-is-my-widget-not-reacting-when-i-click-on-it + /// + /// In `imgui-rs` the same applies, we can manually specify labels with the `##` syntax: + /// + /// ```no_run + /// # let mut imgui = imgui::Context::create(); + /// # let ui = imgui.frame(); + /// + /// ui.button("Click##button1"); + /// ui.button("Click##button2"); + /// ``` + /// + /// Here `Click` is the label used for both buttons, and everything after `##` is used as the identifier. + /// + /// However when you either have many items (say, created in a loop), we can use our loop number as an item in the "ID stack": + /// + /// ```no_run + /// # let mut imgui = imgui::Context::create(); + /// # let ui = imgui.frame(); + /// + /// ui.window("Example").build(|| { + /// // The window adds "Example" to the token stack. + /// for num in 0..10 { + /// // And now we add the loop number to the stack too, + /// // to make our buttons unique within this window. + /// let _id = ui.push_id(num); + /// if ui.button("Click!") { + /// println!("Button {} clicked", num); + /// } + /// } + /// }); + /// ``` + /// + /// We don't have to use numbers - strings also work: + /// + /// ```no_run + /// # let mut imgui = imgui::Context::create(); + /// # let ui = imgui.frame(); + /// + /// fn callback1(ui: &imgui::Ui) { + /// if ui.button("Click") { + /// println!("First button clicked") + /// } + /// } + /// + /// fn callback2(ui: &imgui::Ui) { + /// if ui.button("Click") { + /// println!("Second button clicked") + /// } + /// } + /// + /// ui.window("Example") + /// .build(||{ + /// { + /// // Since we don't know what callback1 might do, we create + /// // a unique ID stack by pushing (a hash of) "first" to the ID stack: + /// let _id1 = ui.push_id("first"); + /// callback1(&ui); + /// } + /// { + /// // Same for second callback. If we didn't do this, clicking the "Click" button + /// // would trigger both println statements! + /// let _id2 = ui.push_id("second"); + /// callback2(&ui); + /// } + /// }); + /// ``` #[doc(alias = "PushId")] pub fn push_id<'a, I: Into>>(&self, id: I) -> IdStackToken<'ui> { let id = id.into(); diff --git a/imgui/src/window/child_window.rs b/imgui/src/window/child_window.rs index 413c28f..741c657 100644 --- a/imgui/src/window/child_window.rs +++ b/imgui/src/window/child_window.rs @@ -1,15 +1,15 @@ use std::f32; -use std::os::raw::{c_char, c_void}; use crate::sys; use crate::window::WindowFlags; -use crate::{Id, Ui}; +use crate::Ui; /// Builder for a child window #[derive(Copy, Clone, Debug)] #[must_use] -pub struct ChildWindow<'a> { - id: Id<'a>, +pub struct ChildWindow<'ui, Label> { + ui: &'ui Ui<'ui>, + name: Label, flags: WindowFlags, size: [f32; 2], content_size: [f32; 2], @@ -18,12 +18,13 @@ pub struct ChildWindow<'a> { border: bool, } -impl<'a> ChildWindow<'a> { +impl<'ui, Label: AsRef> ChildWindow<'ui, Label> { /// Creates a new child window builder with the given ID #[doc(alias = "BeginChildID")] - pub fn new>>(id: T) -> ChildWindow<'a> { + pub fn new(ui: &'ui Ui<'ui>, name: Label) -> ChildWindow<'ui, Label> { ChildWindow { - id: id.into(), + ui, + name, flags: WindowFlags::empty(), size: [0.0, 0.0], content_size: [0.0, 0.0], @@ -245,7 +246,7 @@ impl<'a> ChildWindow<'a> { /// rendered, the token must be ended by calling `.end()`. /// /// Returns `None` if the window is not visible and no content should be rendered. - pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { + pub fn begin(self) -> Option> { if self.content_size[0] != 0.0 || self.content_size[1] != 0.0 { unsafe { sys::igSetNextWindowContentSize(self.content_size.into()) }; } @@ -255,22 +256,16 @@ impl<'a> ChildWindow<'a> { if self.bg_alpha.is_finite() { unsafe { sys::igSetNextWindowBgAlpha(self.bg_alpha) }; } - let id = unsafe { - match self.id { - Id::Int(i) => sys::igGetID_Ptr(i as *const c_void), - Id::Ptr(p) => sys::igGetID_Ptr(p), - Id::Str(s) => { - let start = s.as_ptr() as *const c_char; - let end = start.add(s.len()); - sys::igGetID_StrStr(start, end) - } - } - }; let should_render = unsafe { - sys::igBeginChild_ID(id, self.size.into(), self.border, self.flags.bits() as i32) + sys::igBeginChild_Str( + self.ui.scratch_txt(self.name), + self.size.into(), + self.border, + self.flags.bits() as i32, + ) }; if should_render { - Some(ChildWindowToken::new(ui)) + Some(ChildWindowToken::new(self.ui)) } else { unsafe { sys::igEndChild() }; None @@ -281,8 +276,8 @@ impl<'a> ChildWindow<'a> { /// /// Note: the closure is not called if no window content is visible (e.g. window is collapsed /// or fully clipped). - pub fn build T>(self, ui: &Ui<'_>, f: F) -> Option { - self.begin(ui).map(|_window| f()) + pub fn build T>(self, f: F) -> Option { + self.begin().map(|_window| f()) } } diff --git a/imgui/src/window/mod.rs b/imgui/src/window/mod.rs index 04161cd..a0f873c 100644 --- a/imgui/src/window/mod.rs +++ b/imgui/src/window/mod.rs @@ -160,8 +160,9 @@ impl<'ui> Ui<'ui> { /// Builder for a window #[derive(Debug)] #[must_use] -pub struct Window<'a, T> { - name: T, +pub struct Window<'ui, 'a, Label> { + ui: &'ui Ui<'ui>, + name: Label, opened: Option<&'a mut bool>, flags: WindowFlags, pos: [f32; 2], @@ -177,10 +178,11 @@ pub struct Window<'a, T> { bg_alpha: f32, } -impl<'a, T: AsRef> Window<'a, T> { - /// Creates a new window builder with the given name - pub fn new(name: T) -> Self { +impl<'ui, 'a, Label: AsRef> Window<'ui, 'a, Label> { + /// Typically created via [`Ui::window`] + pub fn new(ui: &'ui Ui<'ui>, name: Label) -> Self { Window { + ui, name, opened: None, flags: WindowFlags::empty(), @@ -487,7 +489,7 @@ impl<'a, T: AsRef> Window<'a, T> { /// /// Returns `None` if the window is not visible and no content should be rendered. #[must_use] - pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { + pub fn begin(self) -> Option> { if self.pos_cond != Condition::Never { unsafe { sys::igSetNextWindowPos( @@ -525,7 +527,7 @@ impl<'a, T: AsRef> Window<'a, T> { } let should_render = unsafe { sys::igBegin( - ui.scratch_txt(self.name), + self.ui.scratch_txt(self.name), self.opened .map(|x| x as *mut bool) .unwrap_or(ptr::null_mut()), @@ -533,7 +535,7 @@ impl<'a, T: AsRef> Window<'a, T> { ) }; if should_render { - Some(WindowToken::new(ui)) + Some(WindowToken::new(self.ui)) } else { unsafe { sys::igEnd() }; None @@ -542,10 +544,10 @@ impl<'a, T: AsRef> Window<'a, T> { /// Creates a window and runs a closure to construct the contents. /// Returns the result of the closure, if it is called. /// - /// Note: the closure is not called if no window content is visible (e.g. window is collapsed - /// or fully clipped). - pub fn build R>(self, ui: &Ui<'_>, f: F) -> Option { - self.begin(ui).map(|_window| f()) + /// Note: the closure is only called if the window content is + /// visible (e.g. will not run if window is collapsed). + pub fn build R>(self, f: F) -> Option { + self.begin().map(|_window| f()) } }