Require explicit ending/popping of almost all stack tokens

This is probably going to be controversial...
Calling end/pop in the Drop implementation causes way too many problems,
and interacts very badly with panics.

There are closure-based simpler implementations of almost everything
except parameter stacks that are still convenient to use. However, if we
end up switching to &mut Ui in most functions (very much possible in the
future!), closures will complicate matters so push/pop and begin/end
pairs are still relevant.
This commit is contained in:
Joonas Javanainen 2019-07-13 17:15:02 +03:00
parent e142f5d1b5
commit 46d099f40f
No known key found for this signature in database
GPG Key ID: D39CCA5CB19B9179
9 changed files with 395 additions and 193 deletions

View File

@ -26,6 +26,10 @@
- Gfx renderer re-exports imgui and gfx
- These functions now take/return PathBuf: log_filename, set_log_filename, ini_filename, set_logfilename
- ID stack manipulation now uses stack tokens
- Parameter stack pushes *must almost always be paired by a manual call to stack pop*
- Container widget tokens *must be ended manually by calling end*.
Closure-based function (e.g. build()) are unaffected and do this
automatically
### Removed

View File

@ -283,13 +283,14 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) {
window = window.opened(opened)
}
window.build(ui, || {
let _token = ui.push_item_width(-140.0);
ui.push_item_width(-140.0);
ui.text(format!("dear imgui says hello. ({})", imgui::dear_imgui_version()));
if let Some(_) = ui.menu_bar() {
if let Some(_) = ui.menu(im_str!("Menu"), true) {
if let Some(menu_bar) = ui.begin_menu_bar() {
if let Some(menu) = ui.begin_menu(im_str!("Menu"), true) {
show_example_menu_file(ui, &mut state.file_menu);
menu.end(ui);
}
if let Some(_) = ui.menu(im_str!("Examples"), true) {
if let Some(menu) = ui.begin_menu(im_str!("Examples"), true) {
MenuItem::new(im_str!("Main menu bar"))
.build_with_ref(ui, &mut state.show_app_main_menu_bar);
MenuItem::new(im_str!("Console"))
@ -312,15 +313,18 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) {
.build_with_ref(ui, &mut state.show_app_manipulating_window_title);
MenuItem::new(im_str!("Custom rendering"))
.build_with_ref(ui, &mut state.show_app_custom_rendering);
menu.end(ui);
}
if let Some(_) = ui.menu(im_str!("Help"), true) {
if let Some(menu) = ui.begin_menu(im_str!("Help"), true) {
MenuItem::new(im_str!("Metrics"))
.build_with_ref(ui, &mut state.show_app_metrics);
MenuItem::new(im_str!("Style Editor"))
.build_with_ref(ui, &mut state.show_app_style_editor);
MenuItem::new(im_str!("About ImGui"))
.build_with_ref(ui, &mut state.show_app_about);
menu.end(ui);
}
menu_bar.end(ui);
}
ui.spacing();
if ui.collapsing_header(im_str!("Help")).build() {
@ -628,7 +632,7 @@ CTRL+click on individual component to input value.\n",
ui.popup_modal(im_str!("Delete?")).always_auto_resize(true).build(|| {
ui.text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n");
ui.separator();
let _token = ui.push_style_var(StyleVar::FramePadding([0.0, 0.0]));
let style = ui.push_style_var(StyleVar::FramePadding([0.0, 0.0]));
ui.checkbox(im_str!("Don't ask me next time"), &mut state.dont_ask_me_next_time);
if ui.button(im_str!("OK"), [120.0, 0.0]) {
@ -638,6 +642,7 @@ CTRL+click on individual component to input value.\n",
if ui.button(im_str!("Cancel"), [120.0, 0.0]) {
ui.close_current_popup();
}
style.pop(ui);
});
if ui.button(im_str!("Stacked modals.."), [0.0, 0.0]) {
@ -674,11 +679,12 @@ CTRL+click on individual component to input value.\n",
}
fn show_example_app_main_menu_bar<'a>(ui: &Ui<'a>, state: &mut State) {
if let Some(_) = ui.main_menu_bar() {
if let Some(_) = ui.menu(im_str!("File"), true) {
if let Some(menu_bar) = ui.begin_main_menu_bar() {
if let Some(menu) = ui.begin_menu(im_str!("File"), true) {
show_example_menu_file(ui, &mut state.file_menu);
menu.end(ui);
}
if let Some(_) = ui.menu(im_str!("Edit"), true) {
if let Some(menu) = ui.begin_menu(im_str!("Edit"), true) {
MenuItem::new(im_str!("Undo"))
.shortcut(im_str!("CTRL+Z"))
.build(ui);
@ -696,7 +702,9 @@ fn show_example_app_main_menu_bar<'a>(ui: &Ui<'a>, state: &mut State) {
MenuItem::new(im_str!("Paste"))
.shortcut(im_str!("CTRL+V"))
.build(ui);
menu.end(ui);
}
menu_bar.end(ui);
}
}
@ -708,24 +716,27 @@ fn show_example_menu_file<'a>(ui: &Ui<'a>, state: &mut FileMenuState) {
MenuItem::new(im_str!("Open"))
.shortcut(im_str!("Ctrl+O"))
.build(ui);
if let Some(_) = ui.menu(im_str!("Open Recent"), true) {
if let Some(menu) = ui.begin_menu(im_str!("Open Recent"), true) {
MenuItem::new(im_str!("fish_hat.c")).build(ui);
MenuItem::new(im_str!("fish_hat.inl")).build(ui);
MenuItem::new(im_str!("fish_hat.h")).build(ui);
if let Some(_) = ui.menu(im_str!("More.."), true) {
if let Some(menu) = ui.begin_menu(im_str!("More.."), true) {
MenuItem::new(im_str!("Hello")).build(ui);
MenuItem::new(im_str!("Sailor")).build(ui);
if let Some(_) = ui.menu(im_str!("Recurse.."), true) {
if let Some(menu) = ui.begin_menu(im_str!("Recurse.."), true) {
show_example_menu_file(ui, state);
menu.end(ui);
}
menu.end(ui);
}
menu.end(ui);
}
MenuItem::new(im_str!("Save"))
.shortcut(im_str!("Ctrl+S"))
.build(ui);
MenuItem::new(im_str!("Save As..")).build(ui);
ui.separator();
if let Some(_) = ui.menu(im_str!("Options"), true) {
if let Some(menu) = ui.begin_menu(im_str!("Options"), true) {
MenuItem::new(im_str!("Enabled")).build_with_ref(ui, &mut state.enabled);
ChildWindow::new("child")
.size([0.0, 60.0])
@ -743,13 +754,15 @@ fn show_example_menu_file<'a>(ui: &Ui<'a>, state: &mut FileMenuState) {
let items = [im_str!("Yes"), im_str!("No"), im_str!("Maybe")];
ComboBox::new(im_str!("Combo")).build_simple_string(ui, &mut state.n, &items);
ui.checkbox(im_str!("Check"), &mut state.b);
menu.end(ui);
}
if let Some(_) = ui.menu(im_str!("Colors"), true) {
if let Some(menu) = ui.begin_menu(im_str!("Colors"), true) {
for &col in StyleColor::VARIANTS.iter() {
MenuItem::new(&im_str!("{:?}", col)).build(ui);
}
menu.end(ui);
}
if let Some(_) = ui.menu(im_str!("Disabled"), false) {
if let Some(_) = ui.begin_menu(im_str!("Disabled"), false) {
unreachable!();
}
MenuItem::new(im_str!("Checked")).selected(true).build(ui);
@ -778,7 +791,7 @@ output your content because that would create a feedback loop.",
fn show_example_app_fixed_overlay(ui: &Ui, opened: &mut bool) {
const DISTANCE: f32 = 10.0;
let window_pos = [DISTANCE, DISTANCE];
let _token = ui.push_style_color(StyleColor::WindowBg, [0.0, 0.0, 0.0, 0.3]);
let style = ui.push_style_color(StyleColor::WindowBg, [0.0, 0.0, 0.0, 0.3]);
Window::new(im_str!("Example: Fixed Overlay"))
.opened(opened)
.position(window_pos, Condition::Always)
@ -797,7 +810,8 @@ fn show_example_app_fixed_overlay(ui: &Ui, opened: &mut bool) {
"Mouse Position: ({:.1},{:.1})",
mouse_pos[0], mouse_pos[1]
));
})
});
style.pop(ui);
}
fn show_example_app_manipulating_window_title(ui: &Ui) {

View File

@ -1,19 +1,32 @@
use std::marker::PhantomData;
use std::ptr;
use std::thread;
use crate::context::Context;
use crate::sys;
use crate::Ui;
/// Represents a layout group
pub struct GroupToken<'ui> {
_ui: PhantomData<&'ui Ui<'ui>>,
/// Tracks a layout group that must be ended by calling `.end()`
#[must_use]
pub struct GroupToken {
ctx: *const Context,
}
impl<'ui> Drop for GroupToken<'ui> {
fn drop(&mut self) {
impl GroupToken {
/// Ends a layout group
pub fn end(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igEndGroup() };
}
}
impl Drop for GroupToken {
fn drop(&mut self) {
if !self.ctx.is_null() && !thread::panicking() {
panic!("A GroupToken was leaked. Did you call .end()?");
}
}
}
/// # Cursor / Layout
impl<'ui> Ui<'ui> {
/// Renders a separator (generally horizontal).
@ -67,9 +80,21 @@ impl<'ui> Ui<'ui> {
/// Groups items together as a single item.
///
/// May be useful to handle the same mouse event on a group of items, for example.
pub fn group(&self) -> GroupToken {
///
/// Returns a `GroupToken` that must be ended by calling `.end()`
#[must_use]
pub fn begin_group(&self) -> GroupToken {
unsafe { sys::igBeginGroup() };
GroupToken { _ui: PhantomData }
GroupToken { ctx: self.ctx }
}
/// Creates a layout group and runs a closure to construct the contents.
///
/// May be useful to handle the same mouse event on a group of items, for example.
pub fn group<R, F: FnOnce() -> R>(&self, f: F) -> R {
let group = self.begin_group();
let result = f();
group.end(self);
result
}
/// Returns the cursor position (in window coordinates)
pub fn cursor_pos(&self) -> [f32; 2] {

View File

@ -1,7 +1,9 @@
use std::marker::PhantomData;
use std::mem;
use std::os::raw::{c_char, c_void};
use std::ptr;
use std::thread;
use crate::context::Context;
use crate::fonts::atlas::FontId;
use crate::internal::RawCast;
use crate::style::{StyleColor, StyleVar};
@ -12,6 +14,8 @@ use crate::{Id, Ui};
impl<'ui> Ui<'ui> {
/// Switches to the given font by pushing it to the font stack.
///
/// Returns a `FontStackToken` that must be popped by calling `.pop()`
///
/// # Panics
///
/// Panics if the font atlas does not contain the given font
@ -26,8 +30,9 @@ impl<'ui> Ui<'ui> {
/// let my_custom_font = ctx.fonts().add_font(&font_data_sources);
/// # let ui = ctx.frame();
/// // During UI construction
/// let _token = ui.push_font(my_custom_font);
/// let font = ui.push_font(my_custom_font);
/// ui.text("I use the custom font!");
/// font.pop(&ui);
/// ```
#[must_use]
pub fn push_font(&self, id: FontId) -> FontStackToken {
@ -36,10 +41,12 @@ impl<'ui> Ui<'ui> {
.get_font(id)
.expect("Font atlas did not contain the given font");
unsafe { sys::igPushFont(font.raw() as *const _ as *mut _) };
FontStackToken { _ui: PhantomData }
FontStackToken { ctx: self.ctx }
}
/// Changes a style color by pushing a change to the color stack.
///
/// Returns a `ColorStackToken` that must be popped by calling `.pop()`
///
/// # Examples
///
/// ```no_run
@ -47,19 +54,22 @@ impl<'ui> Ui<'ui> {
/// # let mut ctx = Context::create();
/// # let ui = ctx.frame();
/// const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
/// let _token = ui.push_style_color(StyleColor::Text, RED);
/// let color = ui.push_style_color(StyleColor::Text, RED);
/// ui.text("I'm red!");
/// color.pop(&ui);
/// ```
#[must_use]
pub fn push_style_color(&self, style_color: StyleColor, color: [f32; 4]) -> ColorStackToken {
unsafe { sys::igPushStyleColor(style_color as i32, color.into()) };
ColorStackToken {
count: 1,
_ui: PhantomData,
ctx: self.ctx,
}
}
/// Changes style colors by pushing several changes to the color stack.
///
/// Returns a `ColorStackToken` that must be popped by calling `.pop()`
///
/// # Examples
///
/// ```no_run
@ -68,12 +78,13 @@ impl<'ui> Ui<'ui> {
/// # let ui = ctx.frame();
/// const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
/// const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0];
/// let _token = ui.push_style_colors(&[
/// let colors = ui.push_style_colors(&[
/// (StyleColor::Text, RED),
/// (StyleColor::TextDisabled, GREEN),
/// ]);
/// ui.text("I'm red!");
/// ui.text_disabled("I'm green!");
/// colors.pop(&ui);
/// ```
#[must_use]
pub fn push_style_colors<'a, I>(&self, style_colors: I) -> ColorStackToken
@ -87,42 +98,48 @@ impl<'ui> Ui<'ui> {
}
ColorStackToken {
count,
_ui: PhantomData,
ctx: self.ctx,
}
}
/// Changes a style variable by pushing a change to the style stack.
///
/// Returns a `StyleStackToken` that must be popped by calling `.pop()`
///
/// # Examples
///
/// ```no_run
/// # use imgui::*;
/// # let mut ctx = Context::create();
/// # let ui = ctx.frame();
/// let _token = ui.push_style_var(StyleVar::Alpha(0.2));
/// let style = ui.push_style_var(StyleVar::Alpha(0.2));
/// ui.text("I'm transparent!");
/// style.pop(&ui);
/// ```
#[must_use]
pub fn push_style_var(&self, style_var: StyleVar) -> StyleStackToken {
unsafe { push_style_var(style_var) };
StyleStackToken {
count: 1,
_ui: PhantomData,
ctx: self.ctx,
}
}
/// Changes style variables by pushing several changes to the style stack.
///
/// Returns a `StyleStackToken` that must be popped by calling `.pop()`
///
/// # Examples
///
/// ```no_run
/// # use imgui::*;
/// # let mut ctx = Context::create();
/// # let ui = ctx.frame();
/// let _token = ui.push_style_vars(&[
/// let styles = ui.push_style_vars(&[
/// StyleVar::Alpha(0.2),
/// StyleVar::ItemSpacing([50.0, 50.0])
/// ]);
/// ui.text("We're transparent...");
/// ui.text("...with large spacing as well");
/// styles.pop(&ui);
/// ```
#[must_use]
pub fn push_style_vars<'a, I>(&self, style_vars: I) -> StyleStackToken
@ -136,46 +153,79 @@ impl<'ui> Ui<'ui> {
}
StyleStackToken {
count,
_ui: PhantomData,
ctx: self.ctx,
}
}
}
/// Represents a change pushed to the font stack
pub struct FontStackToken<'ui> {
_ui: PhantomData<&'ui Ui<'ui>>,
/// Tracks a font pushed to the font stack that must be popped by calling `.pop()`
#[must_use]
pub struct FontStackToken {
ctx: *const Context,
}
impl<'ui> Drop for FontStackToken<'ui> {
fn drop(&mut self) {
impl FontStackToken {
/// Pops a change from the font stack
pub fn pop(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igPopFont() };
}
}
/// Represents one or more changes pushed to the color stack
pub struct ColorStackToken<'ui> {
count: usize,
_ui: PhantomData<&'ui Ui<'ui>>,
impl Drop for FontStackToken {
fn drop(&mut self) {
if !self.ctx.is_null() && !thread::panicking() {
panic!("A FontStackToken was leaked. Did you call .pop()?");
}
}
}
impl<'ui> Drop for ColorStackToken<'ui> {
fn drop(&mut self) {
/// Tracks one or more changes pushed to the color stack that must be popped by calling `.pop()`
#[must_use]
pub struct ColorStackToken {
count: usize,
ctx: *const Context,
}
impl ColorStackToken {
/// Pops changes from the color stack
pub fn pop(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igPopStyleColor(self.count as i32) };
}
}
/// Represents one or more changes pushed to the style stack
pub struct StyleStackToken<'ui> {
count: usize,
_ui: PhantomData<&'ui Ui<'ui>>,
impl Drop for ColorStackToken {
fn drop(&mut self) {
if !self.ctx.is_null() && !thread::panicking() {
panic!("A ColorStackToken was leaked. Did you call .pop()?");
}
}
}
impl<'ui> Drop for StyleStackToken<'ui> {
fn drop(&mut self) {
/// Tracks one or more changes pushed to the style stack that must be popped by calling `.pop()`
#[must_use]
pub struct StyleStackToken {
count: usize,
ctx: *const Context,
}
impl StyleStackToken {
/// Pops changes from the style stack
pub fn pop(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igPopStyleVar(self.count as i32) };
}
}
impl Drop for StyleStackToken {
fn drop(&mut self) {
if !self.ctx.is_null() && !thread::panicking() {
panic!("A StyleStackToken was leaked. Did you call .pop()?");
}
}
}
#[inline]
unsafe fn push_style_var(style_var: StyleVar) {
use crate::style::StyleVar::*;
@ -219,14 +269,15 @@ unsafe fn push_style_var(style_var: StyleVar) {
impl<'ui> Ui<'ui> {
/// Changes the item width by pushing a change to the item width stack.
///
/// Returns an `ItemWidthStackToken` that may be popped by calling `.pop()`
///
/// - `> 0.0`: width is `item_width` pixels
/// - `= 0.0`: default to ~2/3 of window width
/// - `< 0.0`: `item_width` pixels relative to the right of window (-1.0 always aligns width to
/// the right side)
#[must_use]
pub fn push_item_width(&self, item_width: f32) -> ItemWidthStackToken {
unsafe { sys::igPushItemWidth(item_width) };
ItemWidthStackToken { _ui: PhantomData }
ItemWidthStackToken { ctx: self.ctx }
}
/// Sets the width of the next item.
///
@ -243,16 +294,18 @@ impl<'ui> Ui<'ui> {
}
/// Changes the text wrapping position by pushing a change to the text wrapping position stack.
///
/// Returns a `TextWrapPosStackToken` that may be popped by calling `.pop()`
///
/// - `> 0.0`: wrap at `wrap_pos_x` position in window local space
/// - `= 0.0`: wrap to end of window (or column)
/// - `< 0.0`: no wrapping
#[must_use]
pub fn push_text_wrap_pos(&self, wrap_pos_x: f32) -> TextWrapPosStackToken {
unsafe { sys::igPushTextWrapPos(wrap_pos_x) };
TextWrapPosStackToken { _ui: PhantomData }
TextWrapPosStackToken { ctx: self.ctx }
}
/// Changes an item flag by pushing a change to the item flag stack
#[must_use]
/// Changes an item flag by pushing a change to the item flag stack.
///
/// Returns a `ItemFlagsStackToken` that may be popped by calling `.pop()`
pub fn push_item_flag(&self, item_flag: ItemFlag) -> ItemFlagsStackToken {
use self::ItemFlag::*;
match item_flag {
@ -261,7 +314,7 @@ impl<'ui> Ui<'ui> {
}
ItemFlagsStackToken {
discriminant: mem::discriminant(&item_flag),
_ui: PhantomData,
ctx: self.ctx,
}
}
}
@ -273,36 +326,42 @@ pub enum ItemFlag {
ButtonRepeat(bool),
}
/// Represents a change pushed to the item width stack
pub struct ItemWidthStackToken<'ui> {
_ui: PhantomData<&'ui Ui<'ui>>,
/// Tracks a change pushed to the item width stack
pub struct ItemWidthStackToken {
ctx: *const Context,
}
impl<'ui> Drop for ItemWidthStackToken<'ui> {
fn drop(&mut self) {
impl ItemWidthStackToken {
/// Pops a change from the item width stack
pub fn pop(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igPopItemWidth() };
}
}
/// Represents a change pushed to the text wrap position stack
pub struct TextWrapPosStackToken<'ui> {
_ui: PhantomData<&'ui Ui<'ui>>,
/// Tracks a change pushed to the text wrap position stack
pub struct TextWrapPosStackToken {
ctx: *const Context,
}
impl<'ui> Drop for TextWrapPosStackToken<'ui> {
fn drop(&mut self) {
impl TextWrapPosStackToken {
/// Pops a change from the text wrap position stack
pub fn pop(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igPopTextWrapPos() };
}
}
/// Represents a change pushed to the item flags stack
pub struct ItemFlagsStackToken<'ui> {
/// Tracks a change pushed to the item flags stack
pub struct ItemFlagsStackToken {
discriminant: mem::Discriminant<ItemFlag>,
_ui: PhantomData<&'ui Ui<'ui>>,
ctx: *const Context,
}
impl<'ui> Drop for ItemFlagsStackToken<'ui> {
fn drop(&mut self) {
impl ItemFlagsStackToken {
/// Pops a change from the item flags stack
pub fn pop(mut self, _: &Ui) {
self.ctx = ptr::null();
const ALLOW_KEYBOARD_FOCUS: ItemFlag = ItemFlag::AllowKeyboardFocus(true);
const BUTTON_REPEAT: ItemFlag = ItemFlag::ButtonRepeat(true);
@ -318,7 +377,10 @@ impl<'ui> Drop for ItemFlagsStackToken<'ui> {
/// # ID stack
impl<'ui> Ui<'ui> {
/// Pushes an identifier to the ID stack
/// Pushes an identifier to the ID stack.
///
/// Returns an `IdStackToken` that must be popped by calling `.pop()`
///
#[must_use]
pub fn push_id<'a, I: Into<Id<'a>>>(&self, id: I) -> IdStackToken {
let id = id.into();
@ -334,17 +396,28 @@ impl<'ui> Ui<'ui> {
Id::Ptr(p) => sys::igPushIDPtr(p as *const c_void),
}
}
IdStackToken { _ui: PhantomData }
IdStackToken { ctx: self.ctx }
}
}
/// Represents a change pushed to the ID stack
pub struct IdStackToken<'ui> {
_ui: PhantomData<&'ui Ui<'ui>>,
/// Tracks an ID pushed to the ID stack that must be popped by calling `.pop()`
#[must_use]
pub struct IdStackToken {
ctx: *const Context,
}
impl<'ui> Drop for IdStackToken<'ui> {
fn drop(&mut self) {
impl IdStackToken {
/// Pops a change from the ID stack
pub fn pop(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igPopID() };
}
}
impl Drop for IdStackToken {
fn drop(&mut self) {
if !self.ctx.is_null() && !thread::panicking() {
panic!("A IdStackToken was leaked. Did you call .pop()?");
}
}
}

View File

@ -1,8 +1,9 @@
use bitflags::bitflags;
use std::borrow::Cow;
use std::marker::PhantomData;
use std::ptr;
use std::thread;
use crate::context::Context;
use crate::string::ImStr;
use crate::sys;
use crate::Ui;
@ -34,24 +35,24 @@ pub enum ComboBoxPreviewMode {
}
bitflags!(
/// Flags for combo boxes
#[repr(transparent)]
pub struct ComboBoxFlags: u32 {
/// Align the popup toward the left by default
const POPUP_ALIGN_LEFT = sys::ImGuiComboFlags_PopupAlignLeft;
/// Max ~4 items visible.
const HEIGHT_SMALL = sys::ImGuiComboFlags_HeightSmall;
/// Max ~8 items visible (default)
const HEIGHT_REGULAR = sys::ImGuiComboFlags_HeightRegular;
/// Max ~20 items visible
const HEIGHT_LARGE = sys::ImGuiComboFlags_HeightLarge;
/// As many fitting items as possible
const HEIGHT_LARGEST = sys::ImGuiComboFlags_HeightLargest;
/// Display on the preview box without the square arrow button
const NO_ARROW_BUTTON = sys::ImGuiComboFlags_NoArrowButton;
/// Display only a square arrow button
const NO_PREVIEW = sys::ImGuiComboFlags_NoPreview;
}
/// Flags for combo boxes
#[repr(transparent)]
pub struct ComboBoxFlags: u32 {
/// Align the popup toward the left by default
const POPUP_ALIGN_LEFT = sys::ImGuiComboFlags_PopupAlignLeft;
/// Max ~4 items visible.
const HEIGHT_SMALL = sys::ImGuiComboFlags_HeightSmall;
/// Max ~8 items visible (default)
const HEIGHT_REGULAR = sys::ImGuiComboFlags_HeightRegular;
/// Max ~20 items visible
const HEIGHT_LARGE = sys::ImGuiComboFlags_HeightLarge;
/// As many fitting items as possible
const HEIGHT_LARGEST = sys::ImGuiComboFlags_HeightLargest;
/// Display on the preview box without the square arrow button
const NO_ARROW_BUTTON = sys::ImGuiComboFlags_NoArrowButton;
/// Display only a square arrow button
const NO_PREVIEW = sys::ImGuiComboFlags_NoPreview;
}
);
/// Builder for a combo box widget
@ -127,10 +128,14 @@ impl<'a> ComboBox<'a> {
);
self
}
/// Builds this combo box, and starts appending to it.
/// Creates a combo box and starts appending to it.
///
/// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
/// rendered, the token must be ended by calling `.end()`.
///
/// Returns `None` if the combo box is not open and no content should be rendered.
pub fn begin<'ui>(self, _: &'ui Ui<'ui>) -> Option<ComboBoxToken<'ui>> {
#[must_use]
pub fn begin(self, ui: &Ui) -> Option<ComboBoxToken> {
let should_render = unsafe {
sys::igBeginCombo(
self.label.as_ptr(),
@ -139,42 +144,40 @@ impl<'a> ComboBox<'a> {
)
};
if should_render {
Some(ComboBoxToken {
should_end: true,
_ui: PhantomData,
})
Some(ComboBoxToken { ctx: ui.ctx })
} else {
None
}
}
/// Builds the combo box.
/// Creates a combo box and runs a closure to construct the popup contents.
///
/// Note: the closure is not called if the combo box is not open
/// Note: the closure is not called if the combo box is not open.
pub fn build<F: FnOnce()>(self, ui: &Ui, f: F) {
if let Some(_combo) = self.begin(ui) {
if let Some(combo) = self.begin(ui) {
f();
combo.end(ui);
}
}
}
/// Represents a combo box pushed to the window stack
pub struct ComboBoxToken<'ui> {
should_end: bool,
_ui: PhantomData<&'ui Ui<'ui>>,
/// Tracks a combo box that must be ended by calling `.end()`
#[must_use]
pub struct ComboBoxToken {
ctx: *const Context,
}
impl<'ui> ComboBoxToken<'ui> {
/// Finishes the current combo box and pops it from the window stack
pub fn end(mut self) {
self.should_end = false;
impl ComboBoxToken {
/// Ends a combo box
pub fn end(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igEndCombo() };
}
}
impl<'ui> Drop for ComboBoxToken<'ui> {
impl Drop for ComboBoxToken {
fn drop(&mut self) {
if self.should_end {
unsafe { sys::igEndCombo() };
if !self.ctx.is_null() && !thread::panicking() {
panic!("A ComboBoxToken was leaked. Did you call .end()?");
}
}
}

View File

@ -1,6 +1,7 @@
use std::marker::PhantomData;
use std::ptr;
use std::thread;
use crate::context::Context;
use crate::string::ImStr;
use crate::sys;
use crate::Ui;
@ -9,34 +10,73 @@ use crate::Ui;
impl<'ui> Ui<'ui> {
/// Creates and starts appending to a full-screen menu bar.
///
/// Returns `Some(MainMenuBarToken)` if the menu bar is visible. After content has been
/// rendered, the token must be ended by calling `.end()`.
///
/// Returns `None` if the menu bar is not visible and no content should be rendered.
pub fn main_menu_bar(&self) -> Option<MainMenuBarToken> {
#[must_use]
pub fn begin_main_menu_bar(&self) -> Option<MainMenuBarToken> {
if unsafe { sys::igBeginMainMenuBar() } {
Some(MainMenuBarToken { _ui: PhantomData })
Some(MainMenuBarToken { ctx: self.ctx })
} else {
None
}
}
/// Creates a full-screen main menu bar and runs a closure to construct the contents.
///
/// Note: the closure is not called if the menu bar is not visible.
pub fn main_menu_bar<F: FnOnce()>(&self, f: F) {
if let Some(menu_bar) = self.begin_main_menu_bar() {
f();
menu_bar.end(self);
}
}
/// Creates and starts appending to the menu bar of the current window.
///
/// Returns `Some(MenuBarToken)` if the menu bar is visible. After content has been
/// rendered, the token must be ended by calling `.end()`.
///
/// Returns `None` if the menu bar is not visible and no content should be rendered.
pub fn menu_bar(&self) -> Option<MenuBarToken> {
#[must_use]
pub fn begin_menu_bar(&self) -> Option<MenuBarToken> {
if unsafe { sys::igBeginMenuBar() } {
Some(MenuBarToken { _ui: PhantomData })
Some(MenuBarToken { ctx: self.ctx })
} else {
None
}
}
/// Creates a menu bar in the current window and runs a closure to construct the contents.
///
/// Note: the closure is not called if the menu bar is not visible.
pub fn menu_bar<F: FnOnce()>(&self, f: F) {
if let Some(menu_bar) = self.begin_menu_bar() {
f();
menu_bar.end(self);
}
}
/// Creates and starts appending to a sub-menu entry.
///
/// Returns `Some(MenuToken)` if the menu is visible. After content has been
/// rendered, the token must be ended by calling `.end()`.
///
/// Returns `None` if the menu is not visible and no content should be rendered.
pub fn menu(&self, label: &ImStr, enabled: bool) -> Option<MenuToken> {
#[must_use]
pub fn begin_menu(&self, label: &ImStr, enabled: bool) -> Option<MenuToken> {
if unsafe { sys::igBeginMenu(label.as_ptr(), enabled) } {
Some(MenuToken { _ui: PhantomData })
Some(MenuToken { ctx: self.ctx })
} else {
None
}
}
/// Creates a menu and runs a closure to construct the contents.
///
/// Note: the closure is not called if the menu is not visible.
pub fn menu<F: FnOnce()>(&self, label: &ImStr, enabled: bool, f: F) {
if let Some(menu) = self.begin_menu(label, enabled) {
f();
menu.end(self);
}
}
}
/// Builder for a menu item.
@ -111,35 +151,68 @@ impl<'a> MenuItem<'a> {
}
}
/// Represents a main menu bar
pub struct MainMenuBarToken<'ui> {
_ui: PhantomData<&'ui Ui<'ui>>,
/// Tracks a main menu bar that must be ended by calling `.end()`
#[must_use]
pub struct MainMenuBarToken {
ctx: *const Context,
}
impl<'ui> Drop for MainMenuBarToken<'ui> {
fn drop(&mut self) {
impl MainMenuBarToken {
/// Ends a main menu bar
pub fn end(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igEndMainMenuBar() };
}
}
/// Represents a menu bar
pub struct MenuBarToken<'ui> {
_ui: PhantomData<&'ui Ui<'ui>>,
impl Drop for MainMenuBarToken {
fn drop(&mut self) {
if !self.ctx.is_null() && !thread::panicking() {
panic!("A MainMenuBarToken was leaked. Did you call .end()?");
}
}
}
impl<'ui> Drop for MenuBarToken<'ui> {
fn drop(&mut self) {
/// Tracks a menu bar that must be ended by calling `.end()`
#[must_use]
pub struct MenuBarToken {
ctx: *const Context,
}
impl MenuBarToken {
/// Ends a menu bar
pub fn end(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igEndMenuBar() };
}
}
/// Represents a menu
pub struct MenuToken<'ui> {
_ui: PhantomData<&'ui Ui<'ui>>,
impl Drop for MenuBarToken {
fn drop(&mut self) {
if !self.ctx.is_null() && !thread::panicking() {
panic!("A MenuBarToken was leaked. Did you call .end()?");
}
}
}
impl<'ui> Drop for MenuToken<'ui> {
fn drop(&mut self) {
/// Tracks a menu that must be ended by calling `.end()`
#[must_use]
pub struct MenuToken {
ctx: *const Context,
}
impl MenuToken {
/// Ends a menu
pub fn end(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igEndMenu() };
}
}
impl Drop for MenuToken {
fn drop(&mut self) {
if !self.ctx.is_null() && !thread::panicking() {
panic!("A MenuToken was leaked. Did you call .end()?");
}
}
}

View File

@ -24,14 +24,16 @@ impl<'ui> Ui<'ui> {
}
/// Renders simple text using the given text color
pub fn text_colored<T: AsRef<str>>(&self, color: [f32; 4], text: T) {
let _ = self.push_style_color(StyleColor::Text, color);
let style = self.push_style_color(StyleColor::Text, color);
self.text(text);
style.pop(self);
}
/// Renders simple text using `StyleColor::TextDisabled` color
pub fn text_disabled<T: AsRef<str>>(&self, text: T) {
let color = self.style_color(StyleColor::TextDisabled);
let _ = self.push_style_color(StyleColor::Text, color);
let style = self.push_style_color(StyleColor::Text, color);
self.text(text);
style.pop(self);
}
/// Renders text wrapped to the end of window (or column)
pub fn text_wrapped(&self, text: &ImStr) {

View File

@ -1,7 +1,9 @@
use std::f32;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_void};
use std::ptr;
use std::thread;
use crate::context::Context;
use crate::sys;
use crate::window::WindowFlags;
use crate::{Id, Ui};
@ -228,8 +230,13 @@ impl<'a> ChildWindow<'a> {
self.flags |= WindowFlags::NO_INPUTS;
self
}
/// Builds this window, pushes it to the window stack, and starts appending to it
pub fn begin<'ui>(self, _: &'ui Ui<'ui>) -> ChildWindowToken<'ui> {
/// Creates a child window and starts append to it.
///
/// Returns `Some(ChildWindowToken)` if the window is visible. After content has been
/// 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(self, ui: &Ui) -> Option<ChildWindowToken> {
if self.content_size[0] != 0.0 || self.content_size[1] != 0.0 {
unsafe { sys::igSetNextWindowContentSize(self.content_size.into()) };
}
@ -253,45 +260,42 @@ impl<'a> ChildWindow<'a> {
let should_render = unsafe {
sys::igBeginChildID(id, self.size.into(), self.border, self.flags.bits() as i32)
};
ChildWindowToken {
should_render,
should_end: true,
_ui: PhantomData,
if should_render {
Some(ChildWindowToken { ctx: ui.ctx })
} else {
unsafe { sys::igEndChild() };
None
}
}
/// Builds this child window using the given closure to create the window content.
/// Creates a child window and runs a closure to construct the contents.
///
/// Note: the closure is not called if no window content is visible (e.g. window is collapsed
/// or fully clipped).
pub fn build<F: FnOnce()>(self, ui: &Ui, f: F) {
let window = self.begin(ui);
if window.should_render {
if let Some(window) = self.begin(ui) {
f();
window.end(ui);
}
window.end();
}
}
/// Represents a child window pushed to the window stack
pub struct ChildWindowToken<'ui> {
/// True, if the child window contents should be rendered
pub should_render: bool,
should_end: bool,
_ui: PhantomData<&'ui Ui<'ui>>,
/// Tracks a child window that must be ended by calling `.end()`
pub struct ChildWindowToken {
ctx: *const Context,
}
impl<'ui> ChildWindowToken<'ui> {
/// Finishes the current child window and pops it from the window stack
pub fn end(mut self) {
self.should_end = false;
impl ChildWindowToken {
/// Ends a window
pub fn end(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igEndChild() };
}
}
impl<'ui> Drop for ChildWindowToken<'ui> {
impl Drop for ChildWindowToken {
fn drop(&mut self) {
if self.should_end {
unsafe { sys::igEndChild() };
if !self.ctx.is_null() && !thread::panicking() {
panic!("A ChildWindowToken was leaked. Did you call .end()?");
}
}
}

View File

@ -1,8 +1,9 @@
use bitflags::bitflags;
use std::f32;
use std::marker::PhantomData;
use std::ptr;
use std::thread;
use crate::context::Context;
use crate::string::ImStr;
use crate::sys;
use crate::{Condition, Ui};
@ -470,8 +471,14 @@ impl<'a> Window<'a> {
self.flags |= WindowFlags::NO_INPUTS;
self
}
/// Builds this window, pushes it to the window stack, and starts appending to it
pub fn begin<'ui>(self, _: &'ui Ui<'ui>) -> WindowToken<'ui> {
/// Creates a window and starts appending to it.
///
/// Returns `Some(WindowToken)` if the window is visible. After content has been
/// rendered, the token must be ended by calling `.end()`.
///
/// Returns `None` if the window is not visible and no content should be rendered.
#[must_use]
pub fn begin(self, ui: &Ui) -> Option<WindowToken> {
if self.pos_cond != Condition::Never {
unsafe {
sys::igSetNextWindowPos(
@ -516,45 +523,42 @@ impl<'a> Window<'a> {
self.flags.bits() as i32,
)
};
WindowToken {
should_render,
should_end: true,
_ui: PhantomData,
if should_render {
Some(WindowToken { ctx: ui.ctx })
} else {
unsafe { sys::igEnd() };
None
}
}
/// Builds this window using the given closure to create the window content.
/// Creates a window and runs a closure to construct the contents.
///
/// Note: the closure is not called if no window content is visible (e.g. window is collapsed
/// or fully clipped).
pub fn build<F: FnOnce()>(self, ui: &Ui, f: F) {
let window = self.begin(ui);
if window.should_render {
if let Some(window) = self.begin(ui) {
f();
window.end(ui);
}
window.end();
}
}
/// Represents a window pushed to the window stack
pub struct WindowToken<'ui> {
/// True, if the window contents should be rendered
pub should_render: bool,
should_end: bool,
_ui: PhantomData<&'ui Ui<'ui>>,
/// Tracks a window that must be ended by calling `.end()`
pub struct WindowToken {
ctx: *const Context,
}
impl<'ui> WindowToken<'ui> {
/// Finishes the current window and pops it from the window stack
pub fn end(mut self) {
self.should_end = false;
impl WindowToken {
/// Ends a window
pub fn end(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igEnd() };
}
}
impl<'ui> Drop for WindowToken<'ui> {
impl Drop for WindowToken {
fn drop(&mut self) {
if self.should_end {
unsafe { sys::igEnd() };
if !self.ctx.is_null() && !thread::panicking() {
panic!("A WindowToken was leaked. Did you call .end()?");
}
}
}