init significant changes

This commit is contained in:
Jack Mac 2021-09-26 21:07:45 -04:00
parent 4d183cffa6
commit 191a346545
7 changed files with 195 additions and 120 deletions

View File

@ -512,31 +512,36 @@ impl Context {
},
}
}
/// Starts a new frame.
#[deprecated(since = "0.9.0", note = "use `new_frame` instead")]
pub fn frame(&mut self) -> Ui<'_> {
self.new_frame()
}
/// Starts a new frame and returns an `Ui` instance for constructing a user interface.
///
/// # Panics
///
/// Panics if the context uses a shared font atlas that is already borrowed
/// Panics if the context uses a shared font atlas that is already borrowed.
/// Do not attempt to borrow the context afterwards, if you are using a shared font atlas.
#[doc(alias = "NewFame")]
pub fn frame(&mut self) -> Ui<'_> {
pub fn new_frame(&mut self) -> Ui<'_> {
// Clear default font if it no longer exists. This could be an error in the future
let default_font = self.io().font_default;
if !default_font.is_null() && self.fonts().get_font(FontId(default_font)).is_none() {
self.io_mut().font_default = ptr::null_mut();
}
// NewFrame/Render/EndFrame mutate the font atlas so we need exclusive access to it
let font_atlas = self
.shared_font_atlas
.as_ref()
.map(|font_atlas| font_atlas.borrow_mut());
if let Some(font_atlas) = self.shared_font_atlas.as_ref() {
assert!(font_atlas.try_borrow_mut().is_ok());
}
// TODO: precondition checks
unsafe {
sys::igNewFrame();
}
Ui {
ctx: self,
font_atlas,
buffer: crate::UiBuffer::new(1024).into(),
phantom_data: std::marker::PhantomData,
}
}
}

View File

@ -237,7 +237,7 @@ fn test_mouse_down_clicked_released() {
let (_guard, mut ctx) = crate::test::test_ctx_initialized();
{
ctx.io_mut().mouse_down = [false; 5];
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_down(button));
assert!(!ui.is_any_mouse_down());
assert!(!ui.is_mouse_clicked(button));
@ -245,14 +245,14 @@ fn test_mouse_down_clicked_released() {
}
{
ctx.io_mut()[button] = true;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(ui.is_mouse_down(button));
assert!(ui.is_any_mouse_down());
assert!(ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_released(button));
}
{
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(ui.is_mouse_down(button));
assert!(ui.is_any_mouse_down());
assert!(!ui.is_mouse_clicked(button));
@ -260,14 +260,14 @@ fn test_mouse_down_clicked_released() {
}
{
ctx.io_mut()[button] = false;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_down(button));
assert!(!ui.is_any_mouse_down());
assert!(!ui.is_mouse_clicked(button));
assert!(ui.is_mouse_released(button));
}
{
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_down(button));
assert!(!ui.is_any_mouse_down());
assert!(!ui.is_mouse_clicked(button));
@ -287,42 +287,42 @@ fn test_mouse_double_click() {
{
// Pass one second of time
ctx.io_mut().delta_time = 1.0;
let _ = ctx.frame();
let _ = ctx.new_frame();
}
// Fast clicks
ctx.io_mut().delta_time = 1.0 / 60.0;
for &button in MouseButton::VARIANTS.iter() {
{
ctx.io_mut().mouse_down = [false; 5];
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
{
ctx.io_mut()[button] = true;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
{
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
{
ctx.io_mut()[button] = false;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
{
ctx.io_mut()[button] = true;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(ui.is_mouse_clicked(button));
assert!(ui.is_mouse_double_clicked(button));
}
{
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
@ -332,35 +332,35 @@ fn test_mouse_double_click() {
for &button in MouseButton::VARIANTS.iter() {
{
ctx.io_mut().mouse_down = [false; 5];
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
{
ctx.io_mut()[button] = true;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
{
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
{
ctx.io_mut()[button] = false;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
{
ctx.io_mut()[button] = true;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
{
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_clicked(button));
assert!(!ui.is_mouse_double_clicked(button));
}
@ -370,7 +370,7 @@ fn test_mouse_double_click() {
#[test]
fn test_set_get_mouse_cursor() {
let (_guard, mut ctx) = crate::test::test_ctx_initialized();
let ui = ctx.frame();
let ui = ctx.new_frame();
ui.set_mouse_cursor(None);
assert_eq!(None, ui.mouse_cursor());
ui.set_mouse_cursor(Some(MouseCursor::Hand));
@ -384,7 +384,7 @@ fn test_mouse_drags() {
{
ctx.io_mut().mouse_pos = [0.0, 0.0];
ctx.io_mut().mouse_down = [false; 5];
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_dragging(button));
assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 0.0]);
@ -395,7 +395,7 @@ fn test_mouse_drags() {
}
{
ctx.io_mut()[button] = true;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_dragging(button));
assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 0.0]);
@ -406,7 +406,7 @@ fn test_mouse_drags() {
}
{
ctx.io_mut().mouse_pos = [0.0, 100.0];
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(ui.is_mouse_dragging(button));
assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 100.0]);
@ -417,7 +417,7 @@ fn test_mouse_drags() {
}
{
ctx.io_mut().mouse_pos = [0.0, 200.0];
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(ui.is_mouse_dragging(button));
assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 200.0]);
@ -429,7 +429,7 @@ fn test_mouse_drags() {
{
ctx.io_mut().mouse_pos = [10.0, 10.0];
ctx.io_mut()[button] = false;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_dragging(button));
assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
assert_eq!(ui.mouse_drag_delta_with_button(button), [10.0, 10.0]);
@ -440,7 +440,7 @@ fn test_mouse_drags() {
}
{
ctx.io_mut()[button] = true;
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(!ui.is_mouse_dragging(button));
assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 0.0]);
@ -451,7 +451,7 @@ fn test_mouse_drags() {
}
{
ctx.io_mut().mouse_pos = [180.0, 180.0];
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(ui.is_mouse_dragging(button));
assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
assert_eq!(ui.mouse_drag_delta_with_button(button), [170.0, 170.0]);
@ -470,7 +470,7 @@ fn test_mouse_drags() {
}
{
ctx.io_mut().mouse_pos = [200.0, 200.0];
let ui = ctx.frame();
let ui = ctx.new_frame();
assert!(ui.is_mouse_dragging(button));
assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
assert_eq!(ui.mouse_drag_delta_with_button(button), [20.0, 20.0]);

View File

@ -4,6 +4,7 @@
pub extern crate imgui_sys as sys;
use std::cell;
use std::os::raw::{c_char, c_void};
pub use self::clipboard::*;
@ -118,17 +119,37 @@ impl Context {
/// A temporary reference for building the user interface for one frame
#[derive(Debug)]
pub struct Ui<'ui> {
ctx: &'ui Context,
font_atlas: Option<std::cell::RefMut<'ui, SharedFontAtlas>>,
// imgui isn't mutli-threaded -- so no one will ever access twice.
buffer: std::cell::UnsafeCell<string::UiBuffer>,
phantom_data: std::marker::PhantomData<&'ui Context>,
}
/// This is our internal buffer that we use for the Ui object.
///
/// We edit this buffer
static mut BUFFER: cell::UnsafeCell<string::UiBuffer> =
cell::UnsafeCell::new(string::UiBuffer::new(100));
impl<'ui> Ui<'ui> {
/// This provides access to the backing scratch buffer that we use to write
/// strings, along with null-terminators, before we pass normal Rust strs to
/// Dear ImGui.
///
/// This is given as a get-out-of-jail free card if you need to handle the buffer,
/// or, for example, resize it for some reason. Generally, you should never need this.
///
/// ## Safety
///
/// This uses a **static mut** and we assume it will *never* be passed between threads.
/// Do not pass the raw pointer you get between threads at all -- Dear ImGui is single-threaded.
/// We otherwise make no assumptions about the size or keep state in this buffer between calls,
/// so editing the `UiBuffer` is fine.
pub unsafe fn scratch_buffer(&self) -> &cell::UnsafeCell<string::UiBuffer> {
&BUFFER
}
/// Internal method to push a single text to our scratch buffer.
fn scratch_txt(&self, txt: impl AsRef<str>) -> *const sys::cty::c_char {
unsafe {
let handle = &mut *self.buffer.get();
let handle = &mut *BUFFER.get();
handle.scratch_txt(txt)
}
}
@ -136,7 +157,7 @@ impl<'ui> Ui<'ui> {
/// Internal method to push an option text to our scratch buffer.
fn scratch_txt_opt(&self, txt: Option<impl AsRef<str>>) -> *const sys::cty::c_char {
unsafe {
let handle = &mut *self.buffer.get();
let handle = &mut *BUFFER.get();
handle.scratch_txt_opt(txt)
}
}
@ -147,7 +168,7 @@ impl<'ui> Ui<'ui> {
txt_1: impl AsRef<str>,
) -> (*const sys::cty::c_char, *const sys::cty::c_char) {
unsafe {
let handle = &mut *self.buffer.get();
let handle = &mut *BUFFER.get();
handle.scratch_txt_two(txt_0, txt_1)
}
}
@ -158,7 +179,7 @@ impl<'ui> Ui<'ui> {
txt_1: Option<impl AsRef<str>>,
) -> (*const sys::cty::c_char, *const sys::cty::c_char) {
unsafe {
let handle = &mut *self.buffer.get();
let handle = &mut *BUFFER.get();
handle.scratch_txt_with_opt(txt_0, txt_1)
}
}
@ -169,19 +190,13 @@ impl<'ui> Ui<'ui> {
unsafe { &*(sys::igGetIO() as *const Io) }
}
/// Returns an immutable reference to the font atlas
pub fn fonts(&self) -> FontAtlasRef<'_> {
match self.font_atlas {
Some(ref font_atlas) => FontAtlasRef::Shared(font_atlas),
None => unsafe {
let fonts = &*(self.io().fonts as *const FontAtlas);
FontAtlasRef::Owned(fonts)
},
}
/// Returns an immutable reference to the font atlas.
pub fn fonts(&self) -> &FontAtlas {
unsafe { &*(self.io().fonts as *const FontAtlas) }
}
/// Returns a clone of the user interface style
pub fn clone_style(&self) -> Style {
*self.ctx.style()
unsafe { *self.style() }
}
/// Renders the frame and returns a reference to the resulting draw data
#[doc(alias = "Render", alias = "GetDrawData")]
@ -614,7 +629,7 @@ impl<'ui> Ui<'ui> {
height_in_items: i32,
) -> bool {
let (label_ptr, items_inner) = unsafe {
let handle = &mut *self.buffer.get();
let handle = &mut *self.scratch_buffer().get();
handle.refresh_buffer();
let label_ptr = handle.push(label);

View File

@ -1,14 +1,11 @@
use std::mem;
use std::os::raw::{c_char, c_void};
use std::ptr;
use crate::context::Context;
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> Ui<'ui> {
@ -190,9 +187,9 @@ impl<'ui> Ui<'ui> {
/// - `< 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 {
pub fn push_item_width(&self, item_width: f32) -> ItemWidthStackToken<'_> {
unsafe { sys::igPushItemWidth(item_width) };
ItemWidthStackToken { _ctx: self.ctx }
ItemWidthStackToken::new(self)
}
/// Sets the width of the next item.
///
@ -220,7 +217,7 @@ impl<'ui> Ui<'ui> {
///
/// Returns a `TextWrapPosStackToken` that may be popped by calling `.pop()`
#[doc(alias = "PushTextWrapPos")]
pub fn push_text_wrap_pos(&self) -> TextWrapPosStackToken {
pub fn push_text_wrap_pos(&self) -> TextWrapPosStackToken<'_> {
self.push_text_wrap_pos_with_pos(0.0)
}
@ -232,25 +229,58 @@ impl<'ui> Ui<'ui> {
/// - `= 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 {
pub fn push_text_wrap_pos_with_pos(&self, wrap_pos_x: f32) -> TextWrapPosStackToken<'_> {
unsafe { sys::igPushTextWrapPos(wrap_pos_x) };
TextWrapPosStackToken { _ctx: self.ctx }
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()`
#[doc(alias = "PushItemFlag")]
pub fn push_item_flag(&self, item_flag: ItemFlag) -> ItemFlagsStackToken {
///
/// ## 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 {
discriminant: mem::discriminant(&item_flag),
_ctx: self.ctx,
}
ItemFlagsStackToken::new(self, item_flag)
}
}
@ -261,54 +291,78 @@ pub enum ItemFlag {
ButtonRepeat(bool),
}
pub struct ItemWidthStackToken {
_ctx: *const Context,
}
create_token!(
/// Tracks a window that can be ended by calling `.end()`
/// or by dropping.
pub struct ItemWidthStackToken<'ui>;
impl ItemWidthStackToken {
/// Pops a change from the item width stack
/// Ends a window
#[doc(alias = "PopItemWidth")]
pub fn pop(mut self, _: &Ui<'_>) {
self._ctx = ptr::null();
unsafe { sys::igPopItemWidth() };
}
}
drop { sys::igPopItemWidth() }
);
/// Tracks a change pushed to the text wrap position stack
pub struct TextWrapPosStackToken {
_ctx: *const Context,
}
create_token!(
/// Tracks a window that can be ended by calling `.end()`
/// or by dropping.
pub struct TextWrapPosStackToken<'ui>;
impl TextWrapPosStackToken {
/// Pops a change from the text wrap position stack
/// Ends a window
#[doc(alias = "PopTextWrapPos")]
pub fn pop(mut self, _: &Ui<'_>) {
self._ctx = ptr::null();
unsafe { sys::igPopTextWrapPos() };
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<Ui<'a>>,
mem::Discriminant<ItemFlag>,
);
impl<'a> ItemFlagsStackToken<'a> {
/// Creates a new token type.
pub(crate) fn new(_: &crate::Ui<'a>, kind: ItemFlag) -> Self {
Self(std::marker::PhantomData, mem::discriminant(&kind))
}
#[inline]
pub fn end(self) {
// left empty for drop
}
}
/// Tracks a change pushed to the item flags stack
pub struct ItemFlagsStackToken {
discriminant: mem::Discriminant<ItemFlag>,
_ctx: *const Context,
}
impl ItemFlagsStackToken {
/// Pops a change from the item flags stack
#[doc(alias = "PopAllowKeyboardFocus", alias = "PopButtonRepeat")]
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);
if self.discriminant == mem::discriminant(&ALLOW_KEYBOARD_FOCUS) {
unsafe { sys::igPopAllowKeyboardFocus() };
} else if self.discriminant == mem::discriminant(&BUTTON_REPEAT) {
unsafe { sys::igPopButtonRepeat() };
} else {
unreachable!();
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!();
}
}
}
}

View File

@ -7,16 +7,16 @@ use std::{fmt, ptr};
/// this is the unsafe cell upon which we build our abstraction.
#[derive(Debug)]
pub(crate) struct UiBuffer {
buffer: Vec<u8>,
max_len: usize,
pub struct UiBuffer {
pub buffer: Vec<u8>,
pub max_len: usize,
}
impl UiBuffer {
/// Creates a new max buffer with the given length.
pub fn new(max_len: usize) -> Self {
pub const fn new(max_len: usize) -> Self {
Self {
buffer: Vec::with_capacity(max_len),
buffer: Vec::new(),
max_len,
}
}

View File

@ -177,7 +177,7 @@ impl<'ui> Ui<'ui> {
/// style object.
#[doc(alias = "GetStyle")]
pub fn style_color(&self, style_color: StyleColor) -> [f32; 4] {
self.ctx.style()[style_color]
unsafe { self.style() }.colors[style_color as usize]
}
/// Returns a shared reference to the current [`Style`].
@ -193,6 +193,7 @@ impl<'ui> Ui<'ui> {
/// pop. The [`clone_style`](Ui::clone_style) version may instead be used to avoid `unsafe`.
#[doc(alias = "GetStyle")]
pub unsafe fn style(&self) -> &Style {
self.ctx.style()
// safe because Style is a transparent wrapper around sys::ImGuiStyle
&*(sys::igGetStyle() as *const Style)
}
}

View File

@ -215,7 +215,7 @@ where
// we do this ourselves the long way...
unsafe {
let buffer = &mut *ui.buffer.get();
let buffer = &mut *ui.scratch_buffer().get();
buffer.refresh_buffer();
label = buffer.push(self.label);
@ -258,7 +258,7 @@ where
let mut max_display_format = std::ptr::null();
// we do this ourselves the long way...
let buffer = &mut *ui.buffer.get();
let buffer = &mut *ui.scratch_buffer().get();
buffer.refresh_buffer();
label = buffer.push(self.label);