diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index fc8cbba7..13744f54 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -37,14 +37,10 @@ jobs:
- name: Fix Projects
shell: powershell
run: |
- # WARNING: This will need updating if toolset/sdk change in project files!
+ # CI workers do not supporter older Visual Studio versions. Fix projects to target newer available version.
gci -recurse -filter "*.vcxproj" | ForEach-Object {
- # Fix SDK and toolset for most samples.
- (Get-Content $_.FullName) -Replace "v110","v142" | Set-Content -Path $_.FullName
- (Get-Content $_.FullName) -Replace "8.1","10.0.18362.0" | Set-Content -Path $_.FullName
- # Fix SDK and toolset for samples that require newer SDK/toolset. At the moment it is only dx12.
- (Get-Content $_.FullName) -Replace "v140","v142" | Set-Content -Path $_.FullName
- (Get-Content $_.FullName) -Replace "10.0.14393.0","10.0.18362.0" | Set-Content -Path $_.FullName
+ (Get-Content $_.FullName) -Replace "v\d{3}","v142" | Set-Content -Path $_.FullName
+ (Get-Content $_.FullName) -Replace "[\d\.]+","10.0.18362.0" | Set-Content -Path $_.FullName
}
# Not using matrix here because it would inflate job count too much. Check out and setup is done for every job and that makes build times way too long.
@@ -497,34 +493,3 @@ jobs:
run: |
cd examples/example_android_opengl3/android
gradle assembleDebug
-
- Discord-CI:
- runs-on: ubuntu-18.04
- if: always()
- needs: [Windows, Linux, MacOS, iOS, Emscripten, Android]
- steps:
- - uses: dearimgui/github_discord_notifier@latest
- with:
- discord-webhook: ${{ secrets.DISCORD_CI_WEBHOOK }}
- github-token: ${{ github.token }}
- action-task: discord-jobs
- discord-filter: "'{{ github.branch }}'.match(/master|docking/g) != null && '{{ run.conclusion }}' != '{{ last_run.conclusion }}'"
- discord-username: GitHub Actions
- discord-job-new-failure-message: ''
- discord-job-fixed-failure-message: ''
- discord-job-new-failure-embed: |
- {
- "title": "`{{ job.name }}` job is failing on `{{ github.branch }}`!",
- "description": "Commit [{{ github.context.payload.head_commit.title }}]({{ github.context.payload.head_commit.url }}) pushed to [{{ github.branch }}]({{ github.branch_url }}) broke [{{ job.name }}]({{ job.url }}) build job.\nFailing steps: {{ failing_steps }}",
- "url": "{{ job.url }}",
- "color": "0xFF0000",
- "timestamp": "{{ run.updated_at }}"
- }
- discord-job-fixed-failure-embed: |
- {
- "title": "`{{ github.branch }}` branch is no longer failing!",
- "description": "Build failures were fixed on [{{ github.branch }}]({{ github.branch_url }}) branch.",
- "color": "0x00FF00",
- "url": "{{ github.context.payload.head_commit.url }}",
- "timestamp": "{{ run.completed_at }}"
- }
diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp
index bab232d8..9d6b0f70 100644
--- a/backends/imgui_impl_opengl3.cpp
+++ b/backends/imgui_impl_opengl3.cpp
@@ -16,6 +16,7 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
+// 2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers.
// 2021-08-23: OpenGL: Fixed ES 3.0 shader ("#version 300 es") use normal precision floats to avoid wobbly rendering at HD resolutions.
// 2021-08-19: OpenGL: Embed and use our own minimal GL loader (imgui_impl_opengl3_loader.h), removing requirement and support for third-party loader.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
@@ -176,6 +177,8 @@ struct ImGui_ImplOpenGL3_Data
GLuint AttribLocationVtxUV;
GLuint AttribLocationVtxColor;
unsigned int VboHandle, ElementsHandle;
+ GLsizeiptr VertexBufferSize;
+ GLsizeiptr IndexBufferSize;
bool HasClipOrigin;
ImGui_ImplOpenGL3_Data() { memset(this, 0, sizeof(*this)); }
@@ -436,8 +439,20 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
const ImDrawList* cmd_list = draw_data->CmdLists[n];
// Upload vertex/index buffers
- glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW);
+ GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert);
+ GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx);
+ if (bd->VertexBufferSize < vtx_buffer_size)
+ {
+ bd->VertexBufferSize = vtx_buffer_size;
+ glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, NULL, GL_STREAM_DRAW);
+ }
+ if (bd->IndexBufferSize < idx_buffer_size)
+ {
+ bd->IndexBufferSize = idx_buffer_size;
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, NULL, GL_STREAM_DRAW);
+ }
+ glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data);
+ glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data);
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
{
diff --git a/backends/imgui_impl_opengl3_loader.h b/backends/imgui_impl_opengl3_loader.h
index 9313deda..8452301e 100644
--- a/backends/imgui_impl_opengl3_loader.h
+++ b/backends/imgui_impl_opengl3_loader.h
@@ -249,11 +249,13 @@ typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);
typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage);
+typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer);
GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers);
GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers);
GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage);
+GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
#endif
#endif /* GL_VERSION_1_5 */
#ifndef GL_VERSION_2_0
@@ -447,6 +449,7 @@ union GL3WProcs {
PFNGLBLENDEQUATIONSEPARATEPROC BlendEquationSeparate;
PFNGLBLENDFUNCSEPARATEPROC BlendFuncSeparate;
PFNGLBUFFERDATAPROC BufferData;
+ PFNGLBUFFERSUBDATAPROC BufferSubData;
PFNGLCLEARPROC Clear;
PFNGLCLEARCOLORPROC ClearColor;
PFNGLCOMPILESHADERPROC CompileShader;
@@ -506,6 +509,7 @@ GL3W_API extern union GL3WProcs imgl3wProcs;
#define glBlendEquationSeparate imgl3wProcs.gl.BlendEquationSeparate
#define glBlendFuncSeparate imgl3wProcs.gl.BlendFuncSeparate
#define glBufferData imgl3wProcs.gl.BufferData
+#define glBufferSubData imgl3wProcs.gl.BufferSubData
#define glClear imgl3wProcs.gl.Clear
#define glClearColor imgl3wProcs.gl.ClearColor
#define glCompileShader imgl3wProcs.gl.CompileShader
@@ -692,6 +696,7 @@ static const char *proc_names[] = {
"glBlendEquationSeparate",
"glBlendFuncSeparate",
"glBufferData",
+ "glBufferSubData",
"glClear",
"glClearColor",
"glCompileShader",
diff --git a/backends/imgui_impl_osx.h b/backends/imgui_impl_osx.h
index a17bbb4a..d553d42b 100644
--- a/backends/imgui_impl_osx.h
+++ b/backends/imgui_impl_osx.h
@@ -5,11 +5,12 @@
// Implemented features:
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend).
+// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+// [X] Platform: Keyboard arrays indexed using kVK_* codes, e.g. ImGui::IsKeyPressed(kVK_Space).
// Issues:
-// [ ] Platform: Keys are all generally very broken. Best using [event keycode] and not [event characters]..
// [ ] Platform: Multi-viewport / platform windows.
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
@@ -19,7 +20,7 @@
@class NSEvent;
@class NSView;
-IMGUI_IMPL_API bool ImGui_ImplOSX_Init();
+IMGUI_IMPL_API bool ImGui_ImplOSX_Init(NSView* _Nonnull view);
IMGUI_IMPL_API void ImGui_ImplOSX_Shutdown();
IMGUI_IMPL_API void ImGui_ImplOSX_NewFrame(NSView* _Nullable view);
IMGUI_IMPL_API bool ImGui_ImplOSX_HandleEvent(NSEvent* _Nonnull event, NSView* _Nullable view);
diff --git a/backends/imgui_impl_osx.mm b/backends/imgui_impl_osx.mm
index e27b10be..0fc156d1 100644
--- a/backends/imgui_impl_osx.mm
+++ b/backends/imgui_impl_osx.mm
@@ -5,8 +5,9 @@
// Implemented features:
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend).
+// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+// [X] Platform: Keyboard arrays indexed using kVK_* codes, e.g. ImGui::IsKeyPressed(kVK_Space).
// Issues:
-// [ ] Platform: Keys are all generally very broken. Best using [event keycode] and not [event characters]..
// [ ] Platform: Multi-viewport / platform windows.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
@@ -14,13 +15,17 @@
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
-#include "imgui.h"
-#include "imgui_impl_osx.h"
+#import "imgui.h"
+#import "imgui_impl_osx.h"
#import
-#include
+#import
+#import
+#import
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
+// 2021-12-13: *BREAKING CHANGE* Add NSView parameter to ImGui_ImplOSX_Init(). Generally fix keyboard support. Using kVK_* codes for keyboard keys.
+// 2021-12-13: Add game controller support.
// 2021-09-21: Use mach_absolute_time as CFAbsoluteTimeGetCurrent can jump backwards.
// 2021-08-17: Calling io.AddFocusEvent() on NSApplicationDidBecomeActiveNotification/NSApplicationDidResignActiveNotification events.
// 2021-06-23: Inputs: Added a fix for shortcuts using CTRL key instead of CMD key.
@@ -38,15 +43,17 @@
// 2018-07-07: Initial version.
@class ImFocusObserver;
+@class KeyEventResponder;
// Data
-static double g_HostClockPeriod = 0.0;
-static double g_Time = 0.0;
-static NSCursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {};
-static bool g_MouseCursorHidden = false;
-static bool g_MouseJustPressed[ImGuiMouseButton_COUNT] = {};
-static bool g_MouseDown[ImGuiMouseButton_COUNT] = {};
-static ImFocusObserver* g_FocusObserver = NULL;
+static double g_HostClockPeriod = 0.0;
+static double g_Time = 0.0;
+static NSCursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {};
+static bool g_MouseCursorHidden = false;
+static bool g_MouseJustPressed[ImGuiMouseButton_COUNT] = {};
+static bool g_MouseDown[ImGuiMouseButton_COUNT] = {};
+static ImFocusObserver* g_FocusObserver = nil;
+static KeyEventResponder* g_KeyEventResponder = nil;
// Undocumented methods for creating cursors.
@interface NSCursor()
@@ -75,6 +82,102 @@ static void resetKeys()
io.KeyCtrl = io.KeyShift = io.KeyAlt = io.KeySuper = false;
}
+/**
+ KeyEventResponder implements the NSTextInputClient protocol as is required by the macOS text input manager.
+
+ The macOS text input manager is invoked by calling the interpretKeyEvents method from the keyDown method.
+ Keyboard events are then evaluated by the macOS input manager and valid text input is passed back via the
+ insertText:replacementRange method.
+
+ This is the same approach employed by other cross-platform libraries such as SDL2:
+ https://github.com/spurious/SDL-mirror/blob/e17aacbd09e65a4fd1e166621e011e581fb017a8/src/video/cocoa/SDL_cocoakeyboard.m#L53
+ and GLFW:
+ https://github.com/glfw/glfw/blob/b55a517ae0c7b5127dffa79a64f5406021bf9076/src/cocoa_window.m#L722-L723
+ */
+@interface KeyEventResponder: NSView
+@end
+
+@implementation KeyEventResponder
+
+- (void)viewDidMoveToWindow
+{
+ // Ensure self is a first responder to receive the input events.
+ [self.window makeFirstResponder:self];
+}
+
+- (void)keyDown:(NSEvent*)event
+{
+ // Call to the macOS input manager system.
+ [self interpretKeyEvents:@[event]];
+}
+
+- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
+{
+ ImGuiIO& io = ImGui::GetIO();
+
+ NSString* characters;
+ if ([aString isKindOfClass:[NSAttributedString class]])
+ characters = [aString string];
+ else
+ characters = (NSString*)aString;
+
+ io.AddInputCharactersUTF8(characters.UTF8String);
+}
+
+- (BOOL)acceptsFirstResponder
+{
+ return YES;
+}
+
+- (void)doCommandBySelector:(SEL)myselector
+{
+}
+
+- (nullable NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange
+{
+ return nil;
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)point
+{
+ return 0;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange
+{
+ return NSZeroRect;
+}
+
+- (BOOL)hasMarkedText
+{
+ return NO;
+}
+
+- (NSRange)markedRange
+{
+ return NSMakeRange(NSNotFound, 0);
+}
+
+- (NSRange)selectedRange
+{
+ return NSMakeRange(NSNotFound, 0);
+}
+
+- (void)setMarkedText:(nonnull id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
+{
+}
+
+- (void)unmarkText
+{
+}
+
+- (nonnull NSArray*)validAttributesForMarkedText
+{
+ return @[];
+}
+
+@end
+
@interface ImFocusObserver : NSObject
- (void)onApplicationBecomeActive:(NSNotification*)aNotification;
@@ -104,7 +207,7 @@ static void resetKeys()
@end
// Functions
-bool ImGui_ImplOSX_Init()
+bool ImGui_ImplOSX_Init(NSView* view)
{
ImGuiIO& io = ImGui::GetIO();
@@ -116,29 +219,28 @@ bool ImGui_ImplOSX_Init()
io.BackendPlatformName = "imgui_impl_osx";
// Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeyDown[] array.
- const int offset_for_function_keys = 256 - 0xF700;
- io.KeyMap[ImGuiKey_Tab] = '\t';
- io.KeyMap[ImGuiKey_LeftArrow] = NSLeftArrowFunctionKey + offset_for_function_keys;
- io.KeyMap[ImGuiKey_RightArrow] = NSRightArrowFunctionKey + offset_for_function_keys;
- io.KeyMap[ImGuiKey_UpArrow] = NSUpArrowFunctionKey + offset_for_function_keys;
- io.KeyMap[ImGuiKey_DownArrow] = NSDownArrowFunctionKey + offset_for_function_keys;
- io.KeyMap[ImGuiKey_PageUp] = NSPageUpFunctionKey + offset_for_function_keys;
- io.KeyMap[ImGuiKey_PageDown] = NSPageDownFunctionKey + offset_for_function_keys;
- io.KeyMap[ImGuiKey_Home] = NSHomeFunctionKey + offset_for_function_keys;
- io.KeyMap[ImGuiKey_End] = NSEndFunctionKey + offset_for_function_keys;
- io.KeyMap[ImGuiKey_Insert] = NSInsertFunctionKey + offset_for_function_keys;
- io.KeyMap[ImGuiKey_Delete] = NSDeleteFunctionKey + offset_for_function_keys;
- io.KeyMap[ImGuiKey_Backspace] = 127;
- io.KeyMap[ImGuiKey_Space] = 32;
- io.KeyMap[ImGuiKey_Enter] = 13;
- io.KeyMap[ImGuiKey_Escape] = 27;
- io.KeyMap[ImGuiKey_KeyPadEnter] = 3;
- io.KeyMap[ImGuiKey_A] = 'A';
- io.KeyMap[ImGuiKey_C] = 'C';
- io.KeyMap[ImGuiKey_V] = 'V';
- io.KeyMap[ImGuiKey_X] = 'X';
- io.KeyMap[ImGuiKey_Y] = 'Y';
- io.KeyMap[ImGuiKey_Z] = 'Z';
+ io.KeyMap[ImGuiKey_Tab] = kVK_Tab;
+ io.KeyMap[ImGuiKey_LeftArrow] = kVK_LeftArrow;
+ io.KeyMap[ImGuiKey_RightArrow] = kVK_RightArrow;
+ io.KeyMap[ImGuiKey_UpArrow] = kVK_UpArrow;
+ io.KeyMap[ImGuiKey_DownArrow] = kVK_DownArrow;
+ io.KeyMap[ImGuiKey_PageUp] = kVK_PageUp;
+ io.KeyMap[ImGuiKey_PageDown] = kVK_PageDown;
+ io.KeyMap[ImGuiKey_Home] = kVK_Home;
+ io.KeyMap[ImGuiKey_End] = kVK_End;
+ io.KeyMap[ImGuiKey_Insert] = kVK_F13;
+ io.KeyMap[ImGuiKey_Delete] = kVK_ForwardDelete;
+ io.KeyMap[ImGuiKey_Backspace] = kVK_Delete;
+ io.KeyMap[ImGuiKey_Space] = kVK_Space;
+ io.KeyMap[ImGuiKey_Enter] = kVK_Return;
+ io.KeyMap[ImGuiKey_Escape] = kVK_Escape;
+ io.KeyMap[ImGuiKey_KeyPadEnter] = kVK_ANSI_KeypadEnter;
+ io.KeyMap[ImGuiKey_A] = kVK_ANSI_A;
+ io.KeyMap[ImGuiKey_C] = kVK_ANSI_C;
+ io.KeyMap[ImGuiKey_V] = kVK_ANSI_V;
+ io.KeyMap[ImGuiKey_X] = kVK_ANSI_X;
+ io.KeyMap[ImGuiKey_Y] = kVK_ANSI_Y;
+ io.KeyMap[ImGuiKey_Z] = kVK_ANSI_Z;
// Load cursors. Some of them are undocumented.
g_MouseCursorHidden = false;
@@ -191,6 +293,11 @@ bool ImGui_ImplOSX_Init()
name:NSApplicationDidResignActiveNotification
object:nil];
+ // Add the NSTextInputClient to the view hierarchy,
+ // to receive keyboard events and translate them to input text.
+ g_KeyEventResponder = [[KeyEventResponder alloc] initWithFrame:NSZeroRect];
+ [view addSubview:g_KeyEventResponder];
+
return true;
}
@@ -225,8 +332,12 @@ static void ImGui_ImplOSX_UpdateMouseCursorAndButtons()
}
else
{
- // Show OS mouse cursor
- [g_MouseCursors[g_MouseCursors[imgui_cursor] ? imgui_cursor : ImGuiMouseCursor_Arrow] set];
+ NSCursor* desired = g_MouseCursors[imgui_cursor] ?: g_MouseCursors[ImGuiMouseCursor_Arrow];
+ // -[NSCursor set] generates measureable overhead if called unconditionally.
+ if (desired != NSCursor.currentCursor)
+ {
+ [desired set];
+ }
if (g_MouseCursorHidden)
{
g_MouseCursorHidden = false;
@@ -235,6 +346,50 @@ static void ImGui_ImplOSX_UpdateMouseCursorAndButtons()
}
}
+void ImGui_ImplOSX_UpdateGamepads()
+{
+ ImGuiIO& io = ImGui::GetIO();
+ memset(io.NavInputs, 0, sizeof(io.NavInputs));
+ if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
+ return;
+
+ GCController* controller;
+ if (@available(macOS 11.0, *))
+ controller = GCController.current;
+ else
+ controller = GCController.controllers.firstObject;
+
+ if (controller == nil || controller.extendedGamepad == nil)
+ {
+ io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
+ return;
+ }
+
+ GCExtendedGamepad* gp = controller.extendedGamepad;
+
+#define MAP_BUTTON(NAV_NO, NAME) { io.NavInputs[NAV_NO] = gp.NAME.isPressed ? 1.0 : 0.0; }
+ MAP_BUTTON(ImGuiNavInput_Activate, buttonA);
+ MAP_BUTTON(ImGuiNavInput_Cancel, buttonB);
+ MAP_BUTTON(ImGuiNavInput_Menu, buttonX);
+ MAP_BUTTON(ImGuiNavInput_Input, buttonY);
+ MAP_BUTTON(ImGuiNavInput_DpadLeft, dpad.left);
+ MAP_BUTTON(ImGuiNavInput_DpadRight, dpad.right);
+ MAP_BUTTON(ImGuiNavInput_DpadUp, dpad.up);
+ MAP_BUTTON(ImGuiNavInput_DpadDown, dpad.down);
+ MAP_BUTTON(ImGuiNavInput_FocusPrev, leftShoulder);
+ MAP_BUTTON(ImGuiNavInput_FocusNext, rightShoulder);
+ MAP_BUTTON(ImGuiNavInput_TweakSlow, leftTrigger);
+ MAP_BUTTON(ImGuiNavInput_TweakFast, rightTrigger);
+#undef MAP_BUTTON
+
+ io.NavInputs[ImGuiNavInput_LStickLeft] = gp.leftThumbstick.left.value;
+ io.NavInputs[ImGuiNavInput_LStickRight] = gp.leftThumbstick.right.value;
+ io.NavInputs[ImGuiNavInput_LStickUp] = gp.leftThumbstick.up.value;
+ io.NavInputs[ImGuiNavInput_LStickDown] = gp.leftThumbstick.down.value;
+
+ io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
+}
+
void ImGui_ImplOSX_NewFrame(NSView* view)
{
// Setup display size
@@ -257,19 +412,25 @@ void ImGui_ImplOSX_NewFrame(NSView* view)
g_Time = current_time;
ImGui_ImplOSX_UpdateMouseCursorAndButtons();
+ ImGui_ImplOSX_UpdateGamepads();
}
-static int mapCharacterToKey(int c)
+NSString* NSStringFromPhase(NSEventPhase phase)
{
- if (c >= 'a' && c <= 'z')
- return c - 'a' + 'A';
- if (c == 25) // SHIFT+TAB -> TAB
- return 9;
- if (c >= 0 && c < 256)
- return c;
- if (c >= 0xF700 && c < 0xF700 + 256)
- return c - 0xF700 + 256;
- return -1;
+ static NSString* strings[] =
+ {
+ @"none",
+ @"began",
+ @"stationary",
+ @"changed",
+ @"ended",
+ @"cancelled",
+ @"mayBegin",
+ };
+
+ int pos = phase == NSEventPhaseNone ? 0 : __builtin_ctzl((NSUInteger)phase) + 1;
+
+ return strings[pos];
}
bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
@@ -302,6 +463,21 @@ bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
if (event.type == NSEventTypeScrollWheel)
{
+ // Ignore canceled events.
+ //
+ // From macOS 12.1, scrolling with two fingers and then decelerating
+ // by tapping two fingers results in two events appearing:
+ //
+ // 1. A scroll wheel NSEvent, with a phase == NSEventPhaseMayBegin, when the user taps
+ // two fingers to decelerate or stop the scroll events.
+ //
+ // 2. A scroll wheel NSEvent, with a phase == NSEventPhaseCancelled, when the user releases the
+ // two-finger tap. It is this event that sometimes contains large values for scrollingDeltaX and
+ // scrollingDeltaY. When these are added to the current x and y positions of the scrolling view,
+ // it appears to jump up or down. It can be observed in Preview, various JetBrains IDEs and here.
+ if (event.phase == NSEventPhaseCancelled)
+ return false;
+
double wheel_dx = 0.0;
double wheel_dy = 0.0;
@@ -323,6 +499,8 @@ bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
wheel_dy = [event deltaY];
}
+ //NSLog(@"dx=%0.3ff, dy=%0.3f, phase=%@", wheel_dx, wheel_dy, NSStringFromPhase(event.phase));
+
if (fabs(wheel_dx) > 0.0)
io.MouseWheelH += (float)wheel_dx * 0.1f;
if (fabs(wheel_dy) > 0.0)
@@ -330,59 +508,39 @@ bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
return io.WantCaptureMouse;
}
- // FIXME: All the key handling is wrong and broken. Refer to GLFW's cocoa_init.mm and cocoa_window.mm.
- if (event.type == NSEventTypeKeyDown)
+ if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp)
{
- NSString* str = [event characters];
- NSUInteger len = [str length];
- for (NSUInteger i = 0; i < len; i++)
- {
- int c = [str characterAtIndex:i];
- if (!io.KeySuper && !(c >= 0xF700 && c <= 0xFFFF) && c != 127)
- io.AddInputCharacter((unsigned int)c);
-
- // We must reset in case we're pressing a sequence of special keys while keeping the command pressed
- int key = mapCharacterToKey(c);
- if (key != -1 && key < 256 && !io.KeySuper)
- resetKeys();
- if (key != -1)
- io.KeysDown[key] = true;
- }
+ unsigned short code = event.keyCode;
+ IM_ASSERT(code >= 0 && code < IM_ARRAYSIZE(io.KeysDown));
+ io.KeysDown[code] = event.type == NSEventTypeKeyDown;
+ NSEventModifierFlags flags = event.modifierFlags;
+ io.KeyCtrl = (flags & NSEventModifierFlagControl) != 0;
+ io.KeyShift = (flags & NSEventModifierFlagShift) != 0;
+ io.KeyAlt = (flags & NSEventModifierFlagOption) != 0;
+ io.KeySuper = (flags & NSEventModifierFlagCommand) != 0;
return io.WantCaptureKeyboard;
}
- if (event.type == NSEventTypeKeyUp)
+ if (event.type == NSEventTypeFlagsChanged)
{
- NSString* str = [event characters];
- NSUInteger len = [str length];
- for (NSUInteger i = 0; i < len; i++)
+ NSEventModifierFlags flags = event.modifierFlags;
+ switch (event.keyCode)
{
- int c = [str characterAtIndex:i];
- int key = mapCharacterToKey(c);
- if (key != -1)
- io.KeysDown[key] = false;
+ case kVK_Control:
+ io.KeyCtrl = (flags & NSEventModifierFlagControl) != 0;
+ break;
+ case kVK_Shift:
+ io.KeyShift = (flags & NSEventModifierFlagShift) != 0;
+ break;
+ case kVK_Option:
+ io.KeyAlt = (flags & NSEventModifierFlagOption) != 0;
+ break;
+ case kVK_Command:
+ io.KeySuper = (flags & NSEventModifierFlagCommand) != 0;
+ break;
}
return io.WantCaptureKeyboard;
}
- if (event.type == NSEventTypeFlagsChanged)
- {
- unsigned int flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
-
- bool oldKeyCtrl = io.KeyCtrl;
- bool oldKeyShift = io.KeyShift;
- bool oldKeyAlt = io.KeyAlt;
- bool oldKeySuper = io.KeySuper;
- io.KeyCtrl = flags & NSEventModifierFlagControl;
- io.KeyShift = flags & NSEventModifierFlagShift;
- io.KeyAlt = flags & NSEventModifierFlagOption;
- io.KeySuper = flags & NSEventModifierFlagCommand;
-
- // We must reset them as we will not receive any keyUp event if they where pressed with a modifier
- if ((oldKeyShift && !io.KeyShift) || (oldKeyCtrl && !io.KeyCtrl) || (oldKeyAlt && !io.KeyAlt) || (oldKeySuper && !io.KeySuper))
- resetKeys();
- return io.WantCaptureKeyboard;
- }
-
return false;
}
diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt
index f548e785..f4f7baa1 100644
--- a/docs/CHANGELOG.txt
+++ b/docs/CHANGELOG.txt
@@ -107,6 +107,9 @@ Breaking Changes:
- Removed CalcListClipping() function. Prefer using ImGuiListClipper which can return non-contiguous ranges.
Please open an issue if you think you really need this function. (#3841)
+- Backends: OSX: Added NSView* parameter to ImGui_ImplOSX_Init(). (#4759) [@stuartcarnie]
+ Updated Apple+Metal and Apple+GL example applications accordingly.
+
Other Changes:
@@ -134,6 +137,10 @@ Other Changes:
- Nav: with ImGuiConfigFlags_NavEnableSetMousePos enabled: Fixed absolute mouse position when using
Home/End leads to scrolling. Fixed not setting mouse position when a failed move request (e.g. when
already at edge) reactivates the navigation highlight.
+- Menus: fixed closing a menu inside a popup/modal by clicking on the popup/modal. (#3496, #4797)
+- Menus: fixed closing a menu by clicking on its menu-bar item when inside a popup. (#3496, #4797) [@xndcn]
+- Menus: fixed menu inside a popup/modal not inhibiting hovering of items in the popup/modal. (#3496, #4797)
+- Menus: fixed sub-menu items inside a popups from closing the popup.
- InputText, Nav: fixed repeated calls to SetKeyboardFocusHere() preventing to use InputText(). (#4682)
- Inputtext, Nav: fixed using SetKeyboardFocusHere() on InputTextMultiline(). (#4761)
- InputText: made double-click select word, triple-line select line. Word delimitation logic differs
@@ -156,6 +163,8 @@ Other Changes:
- Clipper: fixed invalid state when number of frozen table row is smaller than ItemCount.
- Drag and Drop: BeginDragDropSource() with ImGuiDragDropFlags_SourceAllowNullID doesn't lose
tooltip when scrolling. (#143)
+- Fonts: fixed infinite loop in ImFontGlyphRangesBuilder::AddRanges() when passing UINT16_MAX without
+ the IMGUI_USE_WCHAR32 compile-time option. (#4802) [@SlavicPotato]
- Metrics: Added a node showing windows in submission order and showing the Begin() stack.
- Misc: Added missing ImGuiMouseCursor_NotAllowed cursor for software rendering (when the
io.MouseDrawCursor flag is enabled). (#4713) [@nobody-special666]
@@ -165,12 +174,18 @@ Other Changes:
- Backends: Vulkan: Call vkCmdSetScissor() at the end of render with a full-viewport to reduce
likehood of issues with people using VK_DYNAMIC_STATE_SCISSOR in their app without calling
vkCmdSetScissor() explicitly every frame. (#4644)
+- Backends: OpenGL3: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports
+ with some Intel HD drivers, and perhaps improve performances. (#4468, #4504, #2981, #3381) [@parbo]
- Backends: OpenGL2, Allegro5, Marmalade: Fixed mishandling of the ImDrawCmd::IdxOffset field.
This is an old bug, but due to the way we created drawlists, it never had any visible side-effect before.
The new code for handling Modal and CTRL+Tab dimming/whitening recently made the bug surface. (#4790)
- Backends: DX12: Fixed DRAW_EMPTY_SCISSOR_RECTANGLE warnings. (#4775)
- Backends: SDL_Renderer: Added support for large meshes (64k+ vertices) with 16-bit indices,
enabling 'ImGuiBackendFlags_RendererHasVtxOffset' in the backend. (#3926) [@rokups]
+- Backends: OSX: Generally fix keyboard support. Keyboard arrays indexed using kVK_* codes, e.g.
+ ImGui::IsKeyPressed(kVK_Space). Don't set mouse cursor shape unconditionally. Handle two fingers scroll
+ cancel event. (#4759, #4253, #1873) [@stuartcarnie]
+- Backends: OSX: Add Game Controller support (need linking GameController framework) (#4759) [@stuartcarnie]
- Backends: WebGPU: Passing explicit buffer sizes to wgpuRenderPassEncoderSetVertexBuffer() and
wgpuRenderPassEncoderSetIndexBuffer() functions as validation layers appears to not do what the
in-flux specs says. (#4766) [@meshula]
diff --git a/docs/README.md b/docs/README.md
index 7997a80b..58fcdb20 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -201,10 +201,10 @@ Ongoing Dear ImGui development is currently financially supported by users and p
- [Blizzard](https://careers.blizzard.com/en-us/openings/engineering/all/all/all/1)
*Double-chocolate sponsors*
-- [Google](https://github.com/google/filament), [Nvidia](https://developer.nvidia.com/nvidia-omniverse), [Ubisoft](https://montreal.ubisoft.com/en/ubisoft-sponsors-user-interface-library-for-c-dear-imgui)
+- [Ubisoft](https://montreal.ubisoft.com/en/ubisoft-sponsors-user-interface-library-for-c-dear-imgui), [Supercell](https://supercell.com)
*Chocolate sponsors*
-- [Activision](https://careers.activision.com/c/programmingsoftware-engineering-jobs), [Adobe](https://www.adobe.com/products/medium.html), [Aras Pranckevičius](https://aras-p.info), [Arkane Studios](https://www.arkane-studios.com), [Epic](https://www.unrealengine.com/en-US/megagrants), [RAD Game Tools](http://www.radgametools.com/), [Supercell](https://supercell.com)
+- [Activision](https://careers.activision.com/c/programmingsoftware-engineering-jobs), [Adobe](https://www.adobe.com/products/medium.html), [Aras Pranckevičius](https://aras-p.info), [Arkane Studios](https://www.arkane-studios.com), [Epic](https://www.unrealengine.com/en-US/megagrants), [Google](https://github.com/google/filament), [Nvidia](https://developer.nvidia.com/nvidia-omniverse), [RAD Game Tools](http://www.radgametools.com/)
*Salty-caramel sponsors*
- [Framefield](http://framefield.com), [Grinding Gear Games](https://www.grindinggear.com), [Kylotonn](https://www.kylotonn.com), [Next Level Games](https://www.nextlevelgames.com), [O-Net Communications (USA)](http://en.o-netcom.com)
diff --git a/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj b/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj
index 040fcd64..4bb4fc28 100644
--- a/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj
+++ b/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 05318E0F274C397200A8DE2E /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05318E0E274C397200A8DE2E /* GameController.framework */; };
07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82ED72139413C0078D120 /* imgui_widgets.cpp */; };
07A82ED92139418F0078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82ED72139413C0078D120 /* imgui_widgets.cpp */; };
5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5079822D257677DB0038A28D /* imgui_tables.cpp */; };
@@ -32,6 +33,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 05318E0E274C397200A8DE2E /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
07A82ED62139413C0078D120 /* imgui_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_internal.h; path = ../../imgui_internal.h; sourceTree = ""; };
07A82ED72139413C0078D120 /* imgui_widgets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_widgets.cpp; path = ../../imgui_widgets.cpp; sourceTree = ""; };
5079822D257677DB0038A28D /* imgui_tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_tables.cpp; path = ../../imgui_tables.cpp; sourceTree = ""; };
@@ -76,6 +78,7 @@
files = (
8309BDC6253CCCFE0045E2A1 /* AppKit.framework in Frameworks */,
83BBE9EC20EB471700295997 /* MetalKit.framework in Frameworks */,
+ 05318E0F274C397200A8DE2E /* GameController.framework in Frameworks */,
83BBE9ED20EB471700295997 /* Metal.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -133,6 +136,7 @@
83BBE9E320EB46B800295997 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 05318E0E274C397200A8DE2E /* GameController.framework */,
8309BDC5253CCCFE0045E2A1 /* AppKit.framework */,
8309BD8E253CCAAA0045E2A1 /* UIKit.framework */,
83BBE9EE20EB471C00295997 /* ModelIO.framework */,
diff --git a/examples/example_apple_metal/main.mm b/examples/example_apple_metal/main.mm
index 5d4b7710..bbe51d31 100644
--- a/examples/example_apple_metal/main.mm
+++ b/examples/example_apple_metal/main.mm
@@ -119,7 +119,7 @@
return event;
}];
- ImGui_ImplOSX_Init();
+ ImGui_ImplOSX_Init(self.view);
#endif
}
diff --git a/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj b/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj
index 82fb267c..a168373d 100644
--- a/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj
+++ b/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 05E31B59274EF0700083FCB6 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05E31B57274EF0360083FCB6 /* GameController.framework */; };
07A82EDB213941D00078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82EDA213941D00078D120 /* imgui_widgets.cpp */; };
4080A99820B02D340036BA46 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4080A98A20B02CD90036BA46 /* main.mm */; };
4080A9A220B034280036BA46 /* imgui_impl_opengl2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4080A99E20B034280036BA46 /* imgui_impl_opengl2.cpp */; };
@@ -32,6 +33,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 05E31B57274EF0360083FCB6 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
07A82EDA213941D00078D120 /* imgui_widgets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_widgets.cpp; path = ../../imgui_widgets.cpp; sourceTree = ""; };
4080A96B20B029B00036BA46 /* example_osx_opengl2 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = example_osx_opengl2; sourceTree = BUILT_PRODUCTS_DIR; };
4080A98A20B02CD90036BA46 /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = SOURCE_ROOT; };
@@ -57,6 +59,7 @@
files = (
4080A9B520B034EA0036BA46 /* OpenGL.framework in Frameworks */,
4080A9B320B034E40036BA46 /* Cocoa.framework in Frameworks */,
+ 05E31B59274EF0700083FCB6 /* GameController.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -95,6 +98,7 @@
4080A9B120B034E40036BA46 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 05E31B57274EF0360083FCB6 /* GameController.framework */,
4080A9B420B034EA0036BA46 /* OpenGL.framework */,
4080A9B220B034E40036BA46 /* Cocoa.framework */,
);
diff --git a/examples/example_apple_opengl2/main.mm b/examples/example_apple_opengl2/main.mm
index be33a6e8..73b5d8da 100644
--- a/examples/example_apple_opengl2/main.mm
+++ b/examples/example_apple_opengl2/main.mm
@@ -59,7 +59,7 @@
//ImGui::StyleColorsClassic();
// Setup Platform/Renderer backends
- ImGui_ImplOSX_Init();
+ ImGui_ImplOSX_Init(self);
ImGui_ImplOpenGL2_Init();
// Load Fonts
@@ -147,12 +147,9 @@
animationTimer = [NSTimer scheduledTimerWithTimeInterval:0.017 target:self selector:@selector(animationTimerFired:) userInfo:nil repeats:YES];
}
--(void)reshape { [[self openGLContext] update]; [self updateAndDrawDemoView]; }
+-(void)reshape { [super reshape]; [[self openGLContext] update]; [self updateAndDrawDemoView]; }
-(void)drawRect:(NSRect)bounds { [self updateAndDrawDemoView]; }
-(void)animationTimerFired:(NSTimer*)timer { [self setNeedsDisplay:YES]; }
--(BOOL)acceptsFirstResponder { return (YES); }
--(BOOL)becomeFirstResponder { return (YES); }
--(BOOL)resignFirstResponder { return (YES); }
-(void)dealloc { animationTimer = nil; }
//-----------------------------------------------------------------------------------
diff --git a/imgui.cpp b/imgui.cpp
index 8a51bfd8..c34f721c 100644
--- a/imgui.cpp
+++ b/imgui.cpp
@@ -5544,6 +5544,27 @@ static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settin
window->DockOrder = settings->DockOrder;
}
+static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags)
+{
+ ImGuiContext& g = *GImGui;
+ const ImGuiWindowFlags old_flags = window->Flags;
+ const bool child_flag_changed = (new_flags & ImGuiWindowFlags_ChildWindow) != (old_flags & ImGuiWindowFlags_ChildWindow);
+
+ if ((just_created || child_flag_changed) && !(new_flags & ImGuiWindowFlags_ChildWindow))
+ {
+ g.WindowsFocusOrder.push_back(window);
+ window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1);
+ }
+ else if (child_flag_changed && (new_flags & ImGuiWindowFlags_ChildWindow))
+ {
+ IM_ASSERT(g.WindowsFocusOrder[window->FocusOrder] == window);
+ for (int n = window->FocusOrder + 1; n < g.WindowsFocusOrder.Size; n++)
+ g.WindowsFocusOrder[n]->FocusOrder--;
+ g.WindowsFocusOrder.erase(g.WindowsFocusOrder.Data + window->FocusOrder);
+ window->FocusOrder = -1;
+ }
+}
+
static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags)
{
ImGuiContext& g = *GImGui;
@@ -5584,16 +5605,12 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags)
window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);
}
- if (!(flags & ImGuiWindowFlags_ChildWindow))
- {
- g.WindowsFocusOrder.push_back(window);
- window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1);
- }
-
if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)
g.Windows.push_front(window); // Quite slow but rare and only once
else
g.Windows.push_back(window);
+ UpdateWindowInFocusOrderList(window, true, window->Flags);
+
return window;
}
@@ -6279,6 +6296,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
const bool window_just_created = (window == NULL);
if (window_just_created)
window = CreateNewWindow(name, flags);
+ else
+ UpdateWindowInFocusOrderList(window, window_just_created, flags);
// Automatically disable manual moving/resizing when NoInputs is set
if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs)
@@ -6372,6 +6391,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
window_stack_data.StackSizesOnBegin.SetToCurrentState();
g.CurrentWindowStack.push_back(window_stack_data);
g.CurrentWindow = NULL;
+ if (flags & ImGuiWindowFlags_ChildMenu)
+ g.BeginMenuCount++;
if (flags & ImGuiWindowFlags_Popup)
{
@@ -7107,6 +7128,8 @@ void ImGui::End()
// Pop from window stack
g.LastItemData = g.CurrentWindowStack.back().ParentLastItemDataBackup;
+ if (window->Flags & ImGuiWindowFlags_ChildMenu)
+ g.BeginMenuCount--;
if (window->Flags & ImGuiWindowFlags_Popup)
g.BeginPopupStack.pop_back();
g.CurrentWindowStack.back().StackSizesOnBegin.CompareWithCurrentState();
@@ -9302,7 +9325,7 @@ void ImGui::CloseCurrentPopup()
ImGuiWindow* parent_popup_window = g.OpenPopupStack[popup_idx - 1].Window;
bool close_parent = false;
if (popup_window && (popup_window->Flags & ImGuiWindowFlags_ChildMenu))
- if (parent_popup_window == NULL || !(parent_popup_window->Flags & ImGuiWindowFlags_Modal))
+ if (parent_popup_window && !(parent_popup_window->Flags & ImGuiWindowFlags_MenuBar))
close_parent = true;
if (!close_parent)
break;
@@ -9330,7 +9353,7 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags)
char name[20];
if (flags & ImGuiWindowFlags_ChildMenu)
- ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
+ ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginMenuCount); // Recycle windows based on depth
else
ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
@@ -17583,7 +17606,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con
}
ImDrawList* fg_draw_list = viewport ? GetForegroundDrawList(viewport) : NULL; // Render additional visuals into the top-most draw list
- if (window && fg_draw_list && IsItemHovered())
+ if (window && IsItemHovered() && fg_draw_list)
fg_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
if (!node_open)
return;
diff --git a/imgui.h b/imgui.h
index 9f9cd471..c122fffd 100644
--- a/imgui.h
+++ b/imgui.h
@@ -65,7 +65,7 @@ Index of this file:
// Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens)
#define IMGUI_VERSION "1.86 WIP"
-#define IMGUI_VERSION_NUM 18518
+#define IMGUI_VERSION_NUM 18521
#define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx))
#define IMGUI_HAS_TABLE
#define IMGUI_HAS_VIEWPORT // Viewport WIP branch
diff --git a/imgui_demo.cpp b/imgui_demo.cpp
index 720ed162..b69bb588 100644
--- a/imgui_demo.cpp
+++ b/imgui_demo.cpp
@@ -3418,11 +3418,26 @@ static void ShowDemoWindowPopups()
}
// Call the more complete ShowExampleMenuFile which we use in various places of this demo
- if (ImGui::Button("File Menu.."))
+ if (ImGui::Button("With a menu.."))
ImGui::OpenPopup("my_file_popup");
- if (ImGui::BeginPopup("my_file_popup"))
+ if (ImGui::BeginPopup("my_file_popup", ImGuiWindowFlags_MenuBar))
{
- ShowExampleMenuFile();
+ if (ImGui::BeginMenuBar())
+ {
+ if (ImGui::BeginMenu("File"))
+ {
+ ShowExampleMenuFile();
+ ImGui::EndMenu();
+ }
+ if (ImGui::BeginMenu("Edit"))
+ {
+ ImGui::MenuItem("Dummy");
+ ImGui::EndMenu();
+ }
+ ImGui::EndMenuBar();
+ }
+ ImGui::Text("Hello from popup!");
+ ImGui::Button("This is a dummy button..");
ImGui::EndPopup();
}
diff --git a/imgui_draw.cpp b/imgui_draw.cpp
index bcc320d8..04193e73 100644
--- a/imgui_draw.cpp
+++ b/imgui_draw.cpp
@@ -3088,8 +3088,8 @@ void ImFontGlyphRangesBuilder::AddText(const char* text, const char* text_end)
void ImFontGlyphRangesBuilder::AddRanges(const ImWchar* ranges)
{
for (; ranges[0]; ranges += 2)
- for (ImWchar c = ranges[0]; c <= ranges[1]; c++)
- AddChar(c);
+ for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560
+ AddChar((ImWchar)c);
}
void ImFontGlyphRangesBuilder::BuildRanges(ImVector* out_ranges)
diff --git a/imgui_internal.h b/imgui_internal.h
index 152a03f3..f64d3bf2 100644
--- a/imgui_internal.h
+++ b/imgui_internal.h
@@ -1758,6 +1758,7 @@ struct ImGuiContext
ImVectorGroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin()
ImVectorOpenPopupStack; // Which popups are open (persistent)
ImVectorBeginPopupStack; // Which level of BeginPopup() we are in (reset every frame)
+ int BeginMenuCount;
// Viewports
ImVector Viewports; // Active viewports (always 1+, and generally 1 unless multi-viewports are enabled). Each viewports hold their copy of ImDrawData.
@@ -1993,6 +1994,7 @@ struct ImGuiContext
LastActiveIdTimer = 0.0f;
CurrentItemFlags = ImGuiItemFlags_None;
+ BeginMenuCount = 0;
CurrentDpiScale = 0.0f;
CurrentViewport = NULL;
diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp
index 73993853..889e83ad 100644
--- a/imgui_widgets.cpp
+++ b/imgui_widgets.cpp
@@ -3703,11 +3703,11 @@ static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx])) : 1; }
static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
-static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
#ifdef __APPLE__ // FIXME: Move setting to IO structure
#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_MAC
#else
+static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_WIN
#endif
@@ -6883,6 +6883,23 @@ void ImGui::EndMainMenuBar()
End();
}
+static bool IsRootOfOpenMenuSet()
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu))
+ return false;
+
+ // Initially we used 'OpenParentId' to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) based on parent ID.
+ // This would however prevent the use of e.g. PuhsID() user code submitting menus.
+ // Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag,
+ // making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects.
+ // Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup
+ // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first chilld menu.
+ const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size];
+ return (/*upper_popup->OpenParentId == window->IDStack.back() &&*/ upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu));
+}
+
bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
{
ImGuiWindow* window = GetCurrentWindow();
@@ -6895,8 +6912,9 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
// Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
+ // The first menu in a hierarchy isn't so hovering doesn't get accross (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation.
ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
- if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu))
+ if (window->Flags & ImGuiWindowFlags_ChildMenu)
flags |= ImGuiWindowFlags_ChildWindow;
// If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin().
@@ -6915,11 +6933,12 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
g.MenusIdSubmittedThisFrame.push_back(id);
ImVec2 label_size = CalcTextSize(label, NULL, true);
- bool pressed;
- bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back());
+
+ // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window)
+ const bool menuset_is_open = IsRootOfOpenMenuSet();
ImGuiWindow* backed_nav_window = g.NavWindow;
if (menuset_is_open)
- g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
+ g.NavWindow = window;
// The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
// However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
@@ -6929,6 +6948,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
if (!enabled)
BeginDisabled();
const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
+ bool pressed;
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
{
// Menu inside an horizontal menu bar
@@ -7088,13 +7108,19 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
ImVec2 pos = window->DC.CursorPos;
ImVec2 label_size = CalcTextSize(label, NULL, true);
+ const bool menuset_is_open = IsRootOfOpenMenuSet();
+ ImGuiWindow* backed_nav_window = g.NavWindow;
+ if (menuset_is_open)
+ g.NavWindow = window;
+
// We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
// but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.
bool pressed;
PushID(label);
if (!enabled)
BeginDisabled();
- const ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover;
+
+ const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover;
const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
{
@@ -7104,7 +7130,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
- pressed = Selectable("", selected, flags, ImVec2(w, 0.0f));
+ pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f));
PopStyleVar();
RenderText(text_pos, label);
window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
@@ -7119,7 +7145,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
float checkmark_w = IM_FLOOR(g.FontSize * 1.20f);
float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame
float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
- pressed = Selectable("", false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f));
+ pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f));
RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
if (icon_w > 0.0f)
RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
@@ -7136,6 +7162,8 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
if (!enabled)
EndDisabled();
PopID();
+ if (menuset_is_open)
+ g.NavWindow = backed_nav_window;
return pressed;
}