Browse Source

Merge branch 'master' into docking

# Conflicts:
#	backends/imgui_impl_opengl3.cpp
#	backends/imgui_impl_osx.h
#	backends/imgui_impl_osx.mm
#	imgui.cpp
ocornut 3 years ago
parent
commit
cd36acc88b

+ 3 - 38
.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 "<PlatformToolset>v110</PlatformToolset>","<PlatformToolset>v142</PlatformToolset>" | Set-Content -Path $_.FullName
-            (Get-Content $_.FullName) -Replace "<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>","<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>" | 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 "<PlatformToolset>v140</PlatformToolset>","<PlatformToolset>v142</PlatformToolset>" | Set-Content -Path $_.FullName
-            (Get-Content $_.FullName) -Replace "<WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>","<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>" | Set-Content -Path $_.FullName
+            (Get-Content $_.FullName) -Replace "<PlatformToolset>v\d{3}</PlatformToolset>","<PlatformToolset>v142</PlatformToolset>" | Set-Content -Path $_.FullName
+            (Get-Content $_.FullName) -Replace "<WindowsTargetPlatformVersion>[\d\.]+</WindowsTargetPlatformVersion>","<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>" | 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 }}"
-          }

+ 17 - 2
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++)
         {

+ 5 - 0
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",

+ 4 - 3
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);

+ 249 - 91
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 <Cocoa/Cocoa.h>
-#include <mach/mach_time.h>
+#import <mach/mach_time.h>
+#import <Carbon/Carbon.h>
+#import <GameController/GameController.h>
 
 // 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<NSTextInputClient>
+@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<NSAttributedStringKey>*)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;
 }

+ 15 - 0
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]

+ 2 - 2
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)

+ 4 - 0
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 = "<group>"; };
 		07A82ED72139413C0078D120 /* imgui_widgets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_widgets.cpp; path = ../../imgui_widgets.cpp; sourceTree = "<group>"; };
 		5079822D257677DB0038A28D /* imgui_tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_tables.cpp; path = ../../imgui_tables.cpp; sourceTree = "<group>"; };
@@ -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 */,

+ 1 - 1
examples/example_apple_metal/main.mm

@@ -119,7 +119,7 @@
         return event;
     }];
 
-    ImGui_ImplOSX_Init();
+    ImGui_ImplOSX_Init(self.view);
 
 #endif
 }

+ 4 - 0
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 = "<group>"; };
 		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 */,
 			);

+ 2 - 5
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; }
 
 //-----------------------------------------------------------------------------------

+ 32 - 9
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;

+ 1 - 1
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

+ 18 - 3
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();
         }
 

+ 2 - 2
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<ImWchar>* out_ranges)

+ 2 - 0
imgui_internal.h

@@ -1758,6 +1758,7 @@ struct ImGuiContext
     ImVector<ImGuiGroupData>GroupStack;                         // Stack for BeginGroup()/EndGroup() - not inherited by Begin()
     ImVector<ImGuiPopupData>OpenPopupStack;                     // Which popups are open (persistent)
     ImVector<ImGuiPopupData>BeginPopupStack;                    // Which level of BeginPopup() we are in (reset every frame)
+    int                     BeginMenuCount;
 
     // Viewports
     ImVector<ImGuiViewportP*> 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;

+ 36 - 8
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;
 }