Merge pull request #537 from dbr/uiwindowredux

Window::new -> Ui::window attempt 2
This commit is contained in:
Jonathan Spira 2021-09-30 18:33:16 -04:00 committed by GitHub
commit 7b9ce6bb1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 357 additions and 110 deletions

View File

@ -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!");

View File

@ -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",

View File

@ -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);

View File

@ -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");
}
});
});
});
}

View File

@ -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");

View File

@ -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);

View File

@ -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();

View File

@ -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]) {

View File

@ -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);
}
}
}
});
});
}

View File

@ -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!");

View File

@ -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);

View File

@ -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!");

View File

@ -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);

View File

@ -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 \

View File

@ -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);

View File

@ -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,

View File

@ -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<String>) {
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<String>) {
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() {

View File

@ -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 \

View File

@ -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);

View File

@ -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<Label: AsRef<str>>(&'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<Label: AsRef<str>>(&'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,
}

View File

@ -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<Id<'a>>>(&self, id: I) -> IdStackToken<'ui> {
let id = id.into();

View File

@ -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<str>> ChildWindow<'ui, Label> {
/// Creates a new child window builder with the given ID
#[doc(alias = "BeginChildID")]
pub fn new<T: Into<Id<'a>>>(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<ChildWindowToken<'ui>> {
pub fn begin(self) -> Option<ChildWindowToken<'ui>> {
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, F: FnOnce() -> T>(self, ui: &Ui<'_>, f: F) -> Option<T> {
self.begin(ui).map(|_window| f())
pub fn build<T, F: FnOnce() -> T>(self, f: F) -> Option<T> {
self.begin().map(|_window| f())
}
}

View File

@ -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<str>> Window<'a, T> {
/// Creates a new window builder with the given name
pub fn new(name: T) -> Self {
impl<'ui, 'a, Label: AsRef<str>> 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<str>> 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<WindowToken<'ui>> {
pub fn begin(self) -> Option<WindowToken<'ui>> {
if self.pos_cond != Condition::Never {
unsafe {
sys::igSetNextWindowPos(
@ -525,7 +527,7 @@ impl<'a, T: AsRef<str>> 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<str>> 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<str>> 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, F: FnOnce() -> R>(self, ui: &Ui<'_>, f: F) -> Option<R> {
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, F: FnOnce() -> R>(self, f: F) -> Option<R> {
self.begin().map(|_window| f())
}
}