imgui-rs/imgui/src/stacks.rs

482 lines
17 KiB
Rust

use crate::fonts::atlas::FontId;
use crate::internal::RawCast;
use crate::math::MintVec4;
use crate::style::{StyleColor, StyleVar};
use crate::sys;
use crate::{Id, Ui};
use std::mem;
use std::os::raw::{c_char, c_void};
/// # Parameter stacks (shared)
impl 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
///
/// # Examples
///
/// ```no_run
/// # use imgui::*;
/// # let mut ctx = Context::create();
/// # let font_data_sources = [];
/// // At initialization time
/// let my_custom_font = ctx.fonts().add_font(&font_data_sources);
/// # let ui = ctx.frame();
/// // During UI construction
/// let font = ui.push_font(my_custom_font);
/// ui.text("I use the custom font!");
/// font.pop();
/// ```
#[doc(alias = "PushFont")]
pub fn push_font(&self, id: FontId) -> FontStackToken<'_> {
let fonts = self.fonts();
let font = fonts
.get_font(id)
.expect("Font atlas did not contain the given font");
unsafe { sys::igPushFont(font.raw() as *const _ as *mut _) };
FontStackToken::new(self)
}
/// 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
/// # use imgui::*;
/// # let mut ctx = Context::create();
/// # let ui = ctx.frame();
/// const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
/// let color = ui.push_style_color(StyleColor::Text, RED);
/// ui.text("I'm red!");
/// color.pop();
/// ```
#[doc(alias = "PushStyleColorVec4")]
pub fn push_style_color(
&self,
style_color: StyleColor,
color: impl Into<MintVec4>,
) -> ColorStackToken<'_> {
unsafe { sys::igPushStyleColor_Vec4(style_color as i32, color.into().into()) };
ColorStackToken::new(self)
}
/// Changes a style variable by pushing a change to the style stack.
///
/// Returns a `StyleStackToken` that can be popped by calling `.end()`
/// or by allowing to drop.
///
/// # Examples
///
/// ```no_run
/// # use imgui::*;
/// # let mut ctx = Context::create();
/// # let ui = ctx.frame();
/// let style = ui.push_style_var(StyleVar::Alpha(0.2));
/// ui.text("I'm transparent!");
/// style.pop();
/// ```
#[doc(alias = "PushStyleVar")]
pub fn push_style_var(&self, style_var: StyleVar) -> StyleStackToken<'_> {
unsafe { push_style_var(style_var) };
StyleStackToken::new(self)
}
}
create_token!(
/// Tracks a font pushed to the font stack that can be popped by calling `.end()`
/// or by dropping.
pub struct FontStackToken<'ui>;
/// Pops a change from the font stack.
drop { sys::igPopFont() }
);
impl FontStackToken<'_> {
/// Pops a change from the font stack.
pub fn pop(self) {
self.end()
}
}
create_token!(
/// Tracks a color pushed to the color stack that can be popped by calling `.end()`
/// or by dropping.
pub struct ColorStackToken<'ui>;
/// Pops a change from the color stack.
drop { sys::igPopStyleColor(1) }
);
impl ColorStackToken<'_> {
/// Pops a change from the color stack.
pub fn pop(self) {
self.end()
}
}
create_token!(
/// Tracks a style pushed to the style stack that can be popped by calling `.end()`
/// or by dropping.
pub struct StyleStackToken<'ui>;
/// Pops a change from the style stack.
drop { sys::igPopStyleVar(1) }
);
impl StyleStackToken<'_> {
/// Pops a change from the style stack.
pub fn pop(self) {
self.end()
}
}
#[inline]
unsafe fn push_style_var(style_var: StyleVar) {
use crate::style::StyleVar::*;
use crate::sys::{igPushStyleVar_Float, igPushStyleVar_Vec2};
match style_var {
Alpha(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_Alpha as i32, v),
WindowPadding(v) => igPushStyleVar_Vec2(sys::ImGuiStyleVar_WindowPadding as i32, v.into()),
WindowRounding(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_WindowRounding as i32, v),
WindowBorderSize(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_WindowBorderSize as i32, v),
WindowMinSize(v) => igPushStyleVar_Vec2(sys::ImGuiStyleVar_WindowMinSize as i32, v.into()),
WindowTitleAlign(v) => {
igPushStyleVar_Vec2(sys::ImGuiStyleVar_WindowTitleAlign as i32, v.into())
}
ChildRounding(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_ChildRounding as i32, v),
ChildBorderSize(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_ChildBorderSize as i32, v),
PopupRounding(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_PopupRounding as i32, v),
PopupBorderSize(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_PopupBorderSize as i32, v),
FramePadding(v) => igPushStyleVar_Vec2(sys::ImGuiStyleVar_FramePadding as i32, v.into()),
FrameRounding(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_FrameRounding as i32, v),
FrameBorderSize(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_FrameBorderSize as i32, v),
ItemSpacing(v) => igPushStyleVar_Vec2(sys::ImGuiStyleVar_ItemSpacing as i32, v.into()),
ItemInnerSpacing(v) => {
igPushStyleVar_Vec2(sys::ImGuiStyleVar_ItemInnerSpacing as i32, v.into())
}
IndentSpacing(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_IndentSpacing as i32, v),
ScrollbarSize(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_ScrollbarSize as i32, v),
ScrollbarRounding(v) => {
igPushStyleVar_Float(sys::ImGuiStyleVar_ScrollbarRounding as i32, v)
}
GrabMinSize(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_GrabMinSize as i32, v),
GrabRounding(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_GrabRounding as i32, v),
TabRounding(v) => igPushStyleVar_Float(sys::ImGuiStyleVar_TabRounding as i32, v),
ButtonTextAlign(v) => {
igPushStyleVar_Vec2(sys::ImGuiStyleVar_ButtonTextAlign as i32, v.into())
}
SelectableTextAlign(v) => {
igPushStyleVar_Vec2(sys::ImGuiStyleVar_SelectableTextAlign as i32, v.into())
}
}
}
/// # Parameter stacks (current window)
impl 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)
#[doc(alias = "PushItemWith")]
pub fn push_item_width(&self, item_width: f32) -> ItemWidthStackToken<'_> {
unsafe { sys::igPushItemWidth(item_width) };
ItemWidthStackToken::new(self)
}
/// Sets the width of the next item.
///
/// - `> 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)
#[doc(alias = "SetNextItemWidth")]
pub fn set_next_item_width(&self, item_width: f32) {
unsafe { sys::igSetNextItemWidth(item_width) };
}
/// Returns the width of the item given the pushed settings and the current cursor position.
///
/// This is NOT necessarily the width of last item.
#[doc(alias = "CalcItemWidth")]
pub fn calc_item_width(&self) -> f32 {
unsafe { sys::igCalcItemWidth() }
}
/// Changes the text wrapping position to the end of window (or column), which
/// is generally the default.
///
/// This is the same as calling [push_text_wrap_pos_with_pos](Self::push_text_wrap_pos_with_pos)
/// with `wrap_pos_x` set to 0.0.
///
/// Returns a `TextWrapPosStackToken` that may be popped by calling `.pop()`
#[doc(alias = "PushTextWrapPos")]
pub fn push_text_wrap_pos(&self) -> TextWrapPosStackToken<'_> {
self.push_text_wrap_pos_with_pos(0.0)
}
/// 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
#[doc(alias = "PushTextWrapPos")]
pub fn push_text_wrap_pos_with_pos(&self, wrap_pos_x: f32) -> TextWrapPosStackToken<'_> {
unsafe { sys::igPushTextWrapPos(wrap_pos_x) };
TextWrapPosStackToken::new(self)
}
/// Tab stop enable.
/// Allow focusing using TAB/Shift-TAB, enabled by default but you can
/// disable it for certain widgets
///
/// Returns a [PushAllowKeyboardFocusToken] that should be dropped.
#[doc(alias = "PushAllowKeyboardFocus")]
pub fn push_allow_keyboard_focus(&self, allow: bool) -> PushAllowKeyboardFocusToken<'_> {
unsafe { sys::igPushAllowKeyboardFocus(allow) };
PushAllowKeyboardFocusToken::new(self)
}
/// In 'repeat' mode, button_x functions return repeated true in a typematic
/// manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting).
/// Note that you can call IsItemActive() after any Button() to tell if the
/// button is held in the current frame.
///
/// Returns a [PushButtonRepeatToken] that should be dropped.
#[doc(alias = "PushAllowKeyboardFocus")]
pub fn push_button_repeat(&self, allow: bool) -> PushButtonRepeatToken<'_> {
unsafe { sys::igPushButtonRepeat(allow) };
PushButtonRepeatToken::new(self)
}
/// Changes an item flag by pushing a change to the item flag stack.
///
/// Returns a `ItemFlagsStackToken` that may be popped by calling `.pop()`
///
/// ## Deprecated
///
/// This was deprecated in `0.9.0` because it isn't part of the dear imgui design,
/// and supporting it required a manual implementation of its drop token.
///
/// Instead, just use [`push_allow_keyboard_focus`] and [`push_button_repeat`].
///
/// [`push_allow_keyboard_focus`]: Self::push_allow_keyboard_focus
/// [`push_button_repeat`]: Self::push_button_repeat
#[deprecated(
since = "0.9.0",
note = "use `push_allow_keyboard_focus` or `push_button_repeat` instead"
)]
pub fn push_item_flag(&self, item_flag: ItemFlag) -> ItemFlagsStackToken<'_> {
use self::ItemFlag::*;
match item_flag {
AllowKeyboardFocus(v) => unsafe { sys::igPushAllowKeyboardFocus(v) },
ButtonRepeat(v) => unsafe { sys::igPushButtonRepeat(v) },
}
ItemFlagsStackToken::new(self, item_flag)
}
}
/// A temporary change in item flags
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ItemFlag {
AllowKeyboardFocus(bool),
ButtonRepeat(bool),
}
create_token!(
/// Tracks a window that can be ended by calling `.end()`
/// or by dropping.
pub struct ItemWidthStackToken<'ui>;
/// Ends a window
#[doc(alias = "PopItemWidth")]
drop { sys::igPopItemWidth() }
);
create_token!(
/// Tracks a window that can be ended by calling `.end()`
/// or by dropping.
pub struct TextWrapPosStackToken<'ui>;
/// Ends a window
#[doc(alias = "PopTextWrapPos")]
drop { sys::igPopTextWrapPos() }
);
create_token!(
/// Tracks a window that can be ended by calling `.end()`
/// or by dropping.
pub struct PushAllowKeyboardFocusToken<'ui>;
/// Ends a window
#[doc(alias = "PopAllowKeyboardFocus")]
drop { sys::igPopAllowKeyboardFocus() }
);
create_token!(
/// Tracks a window that can be ended by calling `.end()`
/// or by dropping.
pub struct PushButtonRepeatToken<'ui>;
/// Ends a window
#[doc(alias = "PopButtonRepeat")]
drop { sys::igPopButtonRepeat() }
);
/// Tracks a change pushed to the item flags stack.
///
/// The "item flags" stack was a concept invented in imgui-rs that doesn't have an
/// ImGui equivalent. We're phasing these out to make imgui-rs feel simpler to use.
#[must_use]
pub struct ItemFlagsStackToken<'a>(
std::marker::PhantomData<&'a Ui>,
mem::Discriminant<ItemFlag>,
);
impl<'a> ItemFlagsStackToken<'a> {
/// Creates a new token type.
pub(crate) fn new(_: &'a crate::Ui, kind: ItemFlag) -> Self {
Self(std::marker::PhantomData, mem::discriminant(&kind))
}
#[inline]
pub fn end(self) {
// left empty for drop
}
}
impl Drop for ItemFlagsStackToken<'_> {
fn drop(&mut self) {
unsafe {
if self.1 == mem::discriminant(&ItemFlag::AllowKeyboardFocus(true)) {
sys::igPopAllowKeyboardFocus();
} else if self.1 == mem::discriminant(&ItemFlag::ButtonRepeat(true)) {
sys::igPopButtonRepeat();
} else {
unreachable!();
}
}
}
}
create_token!(
/// Tracks an ID pushed to the ID stack that can be popped by calling `.pop()`
/// or by dropping.
pub struct IdStackToken<'ui>;
/// Pops a change from the ID stack
drop { sys::igPopID() }
);
impl IdStackToken<'_> {
/// Pops a change from the ID stack
pub fn pop(self) {
self.end()
}
}
/// # ID stack
impl<'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>>>(&'ui self, id: I) -> IdStackToken<'ui> {
let id = id.into();
unsafe {
match id {
Id::Int(i) => sys::igPushID_Int(i),
Id::Str(s) => {
let start = s.as_ptr() as *const c_char;
let end = start.add(s.len());
sys::igPushID_StrStr(start, end)
}
Id::Ptr(p) => sys::igPushID_Ptr(p as *const c_void),
}
}
IdStackToken::new(self)
}
}