From 62399b5bfec49a5eab808be1a41c2d018d240777 Mon Sep 17 00:00:00 2001 From: Jonathan Hoffstadt Date: Wed, 8 Jul 2026 10:14:55 -0500 Subject: [PATCH 01/19] Examples: Apple + Metal4: add example (#9466, #9451) + Amend edit to EXAMPLES.md, comment build.xml --- .github/workflows/build.yml | 4 + docs/CHANGELOG.txt | 1 + docs/EXAMPLES.md | 8 +- examples/example_apple_metal4/Makefile | 21 + examples/example_apple_metal4/README.md | 10 + .../project.pbxproj | 535 ++++++++++++++++++ .../example_apple_metal4/iOS/Info-iOS.plist | 49 ++ .../iOS/LaunchScreen.storyboard | 27 + .../macOS/Info-macOS.plist | 30 + .../macOS/MainMenu.storyboard | 93 +++ examples/example_apple_metal4/main.mm | 366 ++++++++++++ 11 files changed, 1141 insertions(+), 3 deletions(-) create mode 100644 examples/example_apple_metal4/Makefile create mode 100644 examples/example_apple_metal4/README.md create mode 100644 examples/example_apple_metal4/example_apple_metal4.xcodeproj/project.pbxproj create mode 100644 examples/example_apple_metal4/iOS/Info-iOS.plist create mode 100644 examples/example_apple_metal4/iOS/LaunchScreen.storyboard create mode 100644 examples/example_apple_metal4/macOS/Info-macOS.plist create mode 100644 examples/example_apple_metal4/macOS/MainMenu.storyboard create mode 100644 examples/example_apple_metal4/main.mm diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3980838a4..a7e1095c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -593,6 +593,10 @@ jobs: run: xcodebuild -project examples/example_apple_metal/example_apple_metal.xcodeproj -target example_apple_metal_macos if: github.event_name == 'workflow_run' + #- name: Build macOS example_apple_metal4 + # run: xcodebuild -project examples/example_apple_metal4/example_apple_metal4.xcodeproj -target example_apple_metal_macos + # if: github.event_name == 'workflow_run' + - name: Build macOS example_apple_opengl2 run: xcodebuild -project examples/example_apple_opengl2/example_apple_opengl2.xcodeproj -target example_osx_opengl2 if: github.event_name == 'workflow_run' diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index b0afa23f8..373da111f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -160,6 +160,7 @@ Other Changes: to spawn multiple-thread to manage OpenGL. (#9403) - Examples: - Android: update to AGP 9.2.0 to support Gradle 9.6.0. + - Apple+Metal4: added new example. (#9465, #9451) [@hoffstadt] - OpenGL3+GLFW/SDL2/SDL3: allow Wine compatibility by passing empty GLSL version string to ImGui_ImplOpenGL3_Init() to let backend decide of a GLSL version based on actual GL version obtained. (#9427, #6577) [@perminovVS] diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 3d410477b..6315a3a5c 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -60,10 +60,12 @@ Android + OpenGL3 (ES) example.
[example_apple_metal/](https://github.com/ocornut/imgui/tree/master/examples/example_apple_metal/)
OSX & iOS + Metal example.
-= main.m + imgui_impl_osx.mm + imgui_impl_metal.mm
+= main.mm + imgui_impl_osx.mm + imgui_impl_metal.mm
It is based on the "cross-platform" game template provided with Xcode as of Xcode 9. -(NB: imgui_impl_osx.mm is currently not as feature complete as other platforms backends. -You may prefer to use the GLFW Or SDL backends, which will also support Windows and Linux.) + +[example_apple_metal4/](https://github.com/ocornut/imgui/blob/master/examples/example_apple_metal4/)
+OSX & iOS + Metal4 example, Mac only.
+= main.mm + imgui_impl_osx.mm + imgui_impl_metal4.mm
[example_apple_opengl2/](https://github.com/ocornut/imgui/blob/master/examples/example_apple_opengl2/)
OSX + OpenGL2 example.
diff --git a/examples/example_apple_metal4/Makefile b/examples/example_apple_metal4/Makefile new file mode 100644 index 000000000..35cdac769 --- /dev/null +++ b/examples/example_apple_metal4/Makefile @@ -0,0 +1,21 @@ +# Makefile for example_apple_metal4, for macOS only (**not iOS**) +CXX = clang++ +EXE = example_apple_metal4 +IMGUI_DIR = ../../ +SOURCES = main.mm +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_osx.mm $(IMGUI_DIR)/backends/imgui_impl_metal4.mm + +CXXFLAGS = -std=c++11 -ObjC++ -fobjc-arc -Wall -Wextra -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +FRAMEWORKS = -framework AppKit -framework Metal -framework MetalKit -framework QuartzCore -framework GameController + +all: $(EXE) + +$(EXE): $(SOURCES) + $(CXX) $(CXXFLAGS) $^ $(FRAMEWORKS) -o $@ + +run: all + ./$(EXE) + +clean: + rm -f $(EXE) *.o diff --git a/examples/example_apple_metal4/README.md b/examples/example_apple_metal4/README.md new file mode 100644 index 000000000..240ae0312 --- /dev/null +++ b/examples/example_apple_metal4/README.md @@ -0,0 +1,10 @@ +# iOS / OSX Metal4 example + +## Introduction + +This example shows how to integrate Dear ImGui with Metal4. It is based on the "cross-platform" game template provided with Xcode as of Xcode 9. + +Consider basing your work off the example_glfw_metal/ or example_sdl2_metal/ examples. They are better supported and will be portable unlike this one. + + + diff --git a/examples/example_apple_metal4/example_apple_metal4.xcodeproj/project.pbxproj b/examples/example_apple_metal4/example_apple_metal4.xcodeproj/project.pbxproj new file mode 100644 index 000000000..ed60054fd --- /dev/null +++ b/examples/example_apple_metal4/example_apple_metal4.xcodeproj/project.pbxproj @@ -0,0 +1,535 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 050450AB2768052600AB6805 /* imgui_tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5079822D257677DB0038A28D /* imgui_tables.cpp */; }; + 050450AD276863B000AB6805 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 050450AC276863B000AB6805 /* QuartzCore.framework */; }; + 05318E0F274C397200A8DE2E /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05318E0E274C397200A8DE2E /* GameController.framework */; }; + 05A275442773BEA20084EF39 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05A275432773BEA20084EF39 /* QuartzCore.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 */; }; + 8309BD8F253CCAAA0045E2A1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8309BD8E253CCAAA0045E2A1 /* UIKit.framework */; }; + 8309BDA5253CCC070045E2A1 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDA0253CCBC10045E2A1 /* main.mm */; }; + 8309BDA8253CCC080045E2A1 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDA0253CCBC10045E2A1 /* main.mm */; }; + 8309BDBB253CCCAD0045E2A1 /* imgui_impl_metal4.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDB5253CCC9D0045E2A1 /* imgui_impl_metal4.mm */; }; + 8309BDBE253CCCB60045E2A1 /* imgui_impl_metal4.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDB5253CCC9D0045E2A1 /* imgui_impl_metal4.mm */; }; + 8309BDBF253CCCB60045E2A1 /* imgui_impl_osx.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDB6253CCC9D0045E2A1 /* imgui_impl_osx.mm */; }; + 8309BDC6253CCCFE0045E2A1 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8309BDC5253CCCFE0045E2A1 /* AppKit.framework */; }; + 8309BDFC253CDAB30045E2A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8309BDF7253CDAAE0045E2A1 /* LaunchScreen.storyboard */; }; + 8309BE04253CDAB60045E2A1 /* MainMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8309BDFA253CDAAE0045E2A1 /* MainMenu.storyboard */; }; + 83BBE9E520EB46B900295997 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83BBE9E420EB46B900295997 /* Metal.framework */; }; + 83BBE9E720EB46BD00295997 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83BBE9E620EB46BD00295997 /* MetalKit.framework */; }; + 83BBE9EC20EB471700295997 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83BBE9EA20EB471700295997 /* MetalKit.framework */; }; + 83BBE9ED20EB471700295997 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83BBE9EB20EB471700295997 /* Metal.framework */; }; + 83BBEA0520EB54E700295997 /* imgui_draw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0120EB54E700295997 /* imgui_draw.cpp */; }; + 83BBEA0620EB54E700295997 /* imgui_draw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0120EB54E700295997 /* imgui_draw.cpp */; }; + 83BBEA0720EB54E700295997 /* imgui_demo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0220EB54E700295997 /* imgui_demo.cpp */; }; + 83BBEA0820EB54E700295997 /* imgui_demo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0220EB54E700295997 /* imgui_demo.cpp */; }; + 83BBEA0920EB54E700295997 /* imgui.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0320EB54E700295997 /* imgui.cpp */; }; + 83BBEA0A20EB54E700295997 /* imgui.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0320EB54E700295997 /* imgui.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 050450AC276863B000AB6805 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 05318E0E274C397200A8DE2E /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; }; + 05A2754027728F5B0084EF39 /* imgui_impl_metal4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_impl_metal4.h; path = ../../backends/imgui_impl_metal4.h; sourceTree = ""; }; + 05A2754127728F5B0084EF39 /* imgui_impl_osx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_impl_osx.h; path = ../../backends/imgui_impl_osx.h; sourceTree = ""; }; + 05A275432773BEA20084EF39 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.2.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; + 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 = ""; }; + 8307E7C420E9F9C900473790 /* example_apple_metal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example_apple_metal.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8307E7DA20E9F9C900473790 /* example_apple_metal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example_apple_metal.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8309BD8E253CCAAA0045E2A1 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 8309BDA0253CCBC10045E2A1 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; + 8309BDB5253CCC9D0045E2A1 /* imgui_impl_metal4.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = imgui_impl_metal4.mm; path = ../../backends/imgui_impl_metal4.mm; sourceTree = ""; }; + 8309BDB6253CCC9D0045E2A1 /* imgui_impl_osx.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = imgui_impl_osx.mm; path = ../../backends/imgui_impl_osx.mm; sourceTree = ""; }; + 8309BDC5253CCCFE0045E2A1 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 8309BDF7253CDAAE0045E2A1 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + 8309BDF8253CDAAE0045E2A1 /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; + 8309BDFA253CDAAE0045E2A1 /* MainMenu.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = MainMenu.storyboard; sourceTree = ""; }; + 8309BDFB253CDAAE0045E2A1 /* Info-macOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-macOS.plist"; sourceTree = ""; }; + 83BBE9E420EB46B900295997 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk/System/Library/Frameworks/Metal.framework; sourceTree = DEVELOPER_DIR; }; + 83BBE9E620EB46BD00295997 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk/System/Library/Frameworks/MetalKit.framework; sourceTree = DEVELOPER_DIR; }; + 83BBE9E820EB46C100295997 /* ModelIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ModelIO.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk/System/Library/Frameworks/ModelIO.framework; sourceTree = DEVELOPER_DIR; }; + 83BBE9EA20EB471700295997 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; + 83BBE9EB20EB471700295997 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + 83BBE9EE20EB471C00295997 /* ModelIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ModelIO.framework; path = System/Library/Frameworks/ModelIO.framework; sourceTree = SDKROOT; }; + 83BBEA0020EB54E700295997 /* imgui.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui.h; path = ../../imgui.h; sourceTree = ""; }; + 83BBEA0120EB54E700295997 /* imgui_draw.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_draw.cpp; path = ../../imgui_draw.cpp; sourceTree = ""; }; + 83BBEA0220EB54E700295997 /* imgui_demo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_demo.cpp; path = ../../imgui_demo.cpp; sourceTree = ""; }; + 83BBEA0320EB54E700295997 /* imgui.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui.cpp; path = ../../imgui.cpp; sourceTree = ""; }; + 83BBEA0420EB54E700295997 /* imconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imconfig.h; path = ../../imconfig.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8307E7C120E9F9C900473790 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 05A275442773BEA20084EF39 /* QuartzCore.framework in Frameworks */, + 8309BD8F253CCAAA0045E2A1 /* UIKit.framework in Frameworks */, + 83BBE9E720EB46BD00295997 /* MetalKit.framework in Frameworks */, + 83BBE9E520EB46B900295997 /* Metal.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8307E7D720E9F9C900473790 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 050450AD276863B000AB6805 /* QuartzCore.framework in Frameworks */, + 8309BDC6253CCCFE0045E2A1 /* AppKit.framework in Frameworks */, + 83BBE9EC20EB471700295997 /* MetalKit.framework in Frameworks */, + 05318E0F274C397200A8DE2E /* GameController.framework in Frameworks */, + 83BBE9ED20EB471700295997 /* Metal.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8307E7B520E9F9C700473790 = { + isa = PBXGroup; + children = ( + 83BBE9F020EB544400295997 /* imgui */, + 8309BD9E253CCBA70045E2A1 /* example */, + 8307E7C520E9F9C900473790 /* Products */, + 83BBE9E320EB46B800295997 /* Frameworks */, + ); + sourceTree = ""; + }; + 8307E7C520E9F9C900473790 /* Products */ = { + isa = PBXGroup; + children = ( + 8307E7C420E9F9C900473790 /* example_apple_metal.app */, + 8307E7DA20E9F9C900473790 /* example_apple_metal.app */, + ); + name = Products; + sourceTree = ""; + }; + 8309BD9E253CCBA70045E2A1 /* example */ = { + isa = PBXGroup; + children = ( + 8309BDF6253CDAAE0045E2A1 /* iOS */, + 8309BDF9253CDAAE0045E2A1 /* macOS */, + 8309BDA0253CCBC10045E2A1 /* main.mm */, + ); + name = example; + sourceTree = ""; + }; + 8309BDF6253CDAAE0045E2A1 /* iOS */ = { + isa = PBXGroup; + children = ( + 8309BDF7253CDAAE0045E2A1 /* LaunchScreen.storyboard */, + 8309BDF8253CDAAE0045E2A1 /* Info-iOS.plist */, + ); + path = iOS; + sourceTree = ""; + }; + 8309BDF9253CDAAE0045E2A1 /* macOS */ = { + isa = PBXGroup; + children = ( + 8309BDFA253CDAAE0045E2A1 /* MainMenu.storyboard */, + 8309BDFB253CDAAE0045E2A1 /* Info-macOS.plist */, + ); + path = macOS; + sourceTree = ""; + }; + 83BBE9E320EB46B800295997 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 050450AC276863B000AB6805 /* QuartzCore.framework */, + 05A275432773BEA20084EF39 /* QuartzCore.framework */, + 05318E0E274C397200A8DE2E /* GameController.framework */, + 8309BDC5253CCCFE0045E2A1 /* AppKit.framework */, + 8309BD8E253CCAAA0045E2A1 /* UIKit.framework */, + 83BBE9EE20EB471C00295997 /* ModelIO.framework */, + 83BBE9EB20EB471700295997 /* Metal.framework */, + 83BBE9EA20EB471700295997 /* MetalKit.framework */, + 83BBE9E820EB46C100295997 /* ModelIO.framework */, + 83BBE9E620EB46BD00295997 /* MetalKit.framework */, + 83BBE9E420EB46B900295997 /* Metal.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 83BBE9F020EB544400295997 /* imgui */ = { + isa = PBXGroup; + children = ( + 5079822D257677DB0038A28D /* imgui_tables.cpp */, + 05A2754027728F5B0084EF39 /* imgui_impl_metal4.h */, + 8309BDB5253CCC9D0045E2A1 /* imgui_impl_metal4.mm */, + 05A2754127728F5B0084EF39 /* imgui_impl_osx.h */, + 8309BDB6253CCC9D0045E2A1 /* imgui_impl_osx.mm */, + 83BBEA0420EB54E700295997 /* imconfig.h */, + 83BBEA0320EB54E700295997 /* imgui.cpp */, + 83BBEA0020EB54E700295997 /* imgui.h */, + 83BBEA0220EB54E700295997 /* imgui_demo.cpp */, + 83BBEA0120EB54E700295997 /* imgui_draw.cpp */, + 07A82ED62139413C0078D120 /* imgui_internal.h */, + 07A82ED72139413C0078D120 /* imgui_widgets.cpp */, + ); + name = imgui; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8307E7C320E9F9C900473790 /* example_apple_metal_ios */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8307E7F020E9F9C900473790 /* Build configuration list for PBXNativeTarget "example_apple_metal_ios" */; + buildPhases = ( + 8307E7C020E9F9C900473790 /* Sources */, + 8307E7C120E9F9C900473790 /* Frameworks */, + 8307E7C220E9F9C900473790 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = example_apple_metal_ios; + productName = "imguiex iOS"; + productReference = 8307E7C420E9F9C900473790 /* example_apple_metal.app */; + productType = "com.apple.product-type.application"; + }; + 8307E7D920E9F9C900473790 /* example_apple_metal_macos */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8307E7F320E9F9C900473790 /* Build configuration list for PBXNativeTarget "example_apple_metal_macos" */; + buildPhases = ( + 8307E7D620E9F9C900473790 /* Sources */, + 8307E7D720E9F9C900473790 /* Frameworks */, + 8307E7D820E9F9C900473790 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = example_apple_metal_macos; + productName = "imguiex macOS"; + productReference = 8307E7DA20E9F9C900473790 /* example_apple_metal.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8307E7B620E9F9C700473790 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1530; + ORGANIZATIONNAME = "Warren Moore"; + TargetAttributes = { + 8307E7C320E9F9C900473790 = { + CreatedOnToolsVersion = 9.4.1; + ProvisioningStyle = Automatic; + }; + 8307E7D920E9F9C900473790 = { + CreatedOnToolsVersion = 9.4.1; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 8307E7B920E9F9C700473790 /* Build configuration list for PBXProject "example_apple_metal" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8307E7B520E9F9C700473790; + productRefGroup = 8307E7C520E9F9C900473790 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8307E7C320E9F9C900473790 /* example_apple_metal_ios */, + 8307E7D920E9F9C900473790 /* example_apple_metal_macos */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8307E7C220E9F9C900473790 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8309BDFC253CDAB30045E2A1 /* LaunchScreen.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8307E7D820E9F9C900473790 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8309BE04253CDAB60045E2A1 /* MainMenu.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8307E7C020E9F9C900473790 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8309BDBB253CCCAD0045E2A1 /* imgui_impl_metal4.mm in Sources */, + 83BBEA0920EB54E700295997 /* imgui.cpp in Sources */, + 83BBEA0720EB54E700295997 /* imgui_demo.cpp in Sources */, + 83BBEA0520EB54E700295997 /* imgui_draw.cpp in Sources */, + 5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */, + 07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */, + 8309BDA5253CCC070045E2A1 /* main.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8307E7D620E9F9C900473790 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8309BDBE253CCCB60045E2A1 /* imgui_impl_metal4.mm in Sources */, + 8309BDBF253CCCB60045E2A1 /* imgui_impl_osx.mm in Sources */, + 83BBEA0A20EB54E700295997 /* imgui.cpp in Sources */, + 83BBEA0820EB54E700295997 /* imgui_demo.cpp in Sources */, + 83BBEA0620EB54E700295997 /* imgui_draw.cpp in Sources */, + 050450AB2768052600AB6805 /* imgui_tables.cpp in Sources */, + 07A82ED92139418F0078D120 /* imgui_widgets.cpp in Sources */, + 8309BDA8253CCC080045E2A1 /* main.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 8307E7EE20E9F9C900473790 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + }; + name = Debug; + }; + 8307E7EF20E9F9C900473790 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + }; + name = Release; + }; + 8307E7F120E9F9C900473790 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/iOS/Info-iOS.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.imgui.example.apple-metal-ios"; + PRODUCT_NAME = example_apple_metal; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../**"; + }; + name = Debug; + }; + 8307E7F220E9F9C900473790 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/iOS/Info-iOS.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.imgui.example.apple-metal-ios"; + PRODUCT_NAME = example_apple_metal; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../**"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8307E7F420E9F9C900473790 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/macOS/Info-macOS.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 26.0; + PRODUCT_BUNDLE_IDENTIFIER = "org.imgui.example.apple-metal-macos"; + PRODUCT_NAME = example_apple_metal; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../**"; + }; + name = Debug; + }; + 8307E7F520E9F9C900473790 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/macOS/Info-macOS.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 26.0; + PRODUCT_BUNDLE_IDENTIFIER = "org.imgui.example.apple-metal-macos"; + PRODUCT_NAME = example_apple_metal; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../**"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8307E7B920E9F9C700473790 /* Build configuration list for PBXProject "example_apple_metal" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8307E7EE20E9F9C900473790 /* Debug */, + 8307E7EF20E9F9C900473790 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8307E7F020E9F9C900473790 /* Build configuration list for PBXNativeTarget "example_apple_metal_ios" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8307E7F120E9F9C900473790 /* Debug */, + 8307E7F220E9F9C900473790 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8307E7F320E9F9C900473790 /* Build configuration list for PBXNativeTarget "example_apple_metal_macos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8307E7F420E9F9C900473790 /* Debug */, + 8307E7F520E9F9C900473790 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8307E7B620E9F9C700473790 /* Project object */; +} diff --git a/examples/example_apple_metal4/iOS/Info-iOS.plist b/examples/example_apple_metal4/iOS/Info-iOS.plist new file mode 100644 index 000000000..93ef078d0 --- /dev/null +++ b/examples/example_apple_metal4/iOS/Info-iOS.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + imgui + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + metal + + UIRequiresFullScreen + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/example_apple_metal4/iOS/LaunchScreen.storyboard b/examples/example_apple_metal4/iOS/LaunchScreen.storyboard new file mode 100644 index 000000000..12c52cfbf --- /dev/null +++ b/examples/example_apple_metal4/iOS/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example_apple_metal4/macOS/Info-macOS.plist b/examples/example_apple_metal4/macOS/Info-macOS.plist new file mode 100644 index 000000000..6f4a2b236 --- /dev/null +++ b/examples/example_apple_metal4/macOS/Info-macOS.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + imgui + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSMainStoryboardFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/examples/example_apple_metal4/macOS/MainMenu.storyboard b/examples/example_apple_metal4/macOS/MainMenu.storyboard new file mode 100644 index 000000000..38ad432b0 --- /dev/null +++ b/examples/example_apple_metal4/macOS/MainMenu.storyboard @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example_apple_metal4/main.mm b/examples/example_apple_metal4/main.mm new file mode 100644 index 000000000..1102e41f8 --- /dev/null +++ b/examples/example_apple_metal4/main.mm @@ -0,0 +1,366 @@ +// Dear ImGui: standalone example application for OSX + Metal. + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#import + +#if TARGET_OS_OSX +#import +#else +#import +#endif + +#import +#import + +#include "imgui.h" +#include "imgui_impl_metal4.h" +#if TARGET_OS_OSX +#include "imgui_impl_osx.h" +@interface AppViewController : NSViewController +@end +#else +@interface AppViewController : UIViewController +@end +#endif + +@interface AppViewController () +@property (nonatomic, readonly) MTKView *mtkView; +@property (nonatomic, strong) id device; +@property (nonatomic, strong) id commandQueue; +@property (nonatomic, strong) id commandAllocator; +@end + +#define FRAMES_IN_FLIGHT 2 + +//----------------------------------------------------------------------------------- +// AppViewController +//----------------------------------------------------------------------------------- + +@implementation AppViewController + +-(instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + + _device = MTLCreateSystemDefaultDevice(); + _commandQueue = [_device newMTL4CommandQueue]; + _commandAllocator = [_device newCommandAllocator]; + + if (!self.device) + { + NSLog(@"Metal is not supported"); + abort(); + } + + // Setup Dear ImGui context + // FIXME: This example doesn't have proper cleanup... + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. + + // Setup Renderer backend + ImGui_ImplMetal4_Init(_device, _commandQueue, FRAMES_IN_FLIGHT); + + // Load Fonts + // - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). + // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. + // - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit). + // - Read 'docs/FONTS.md' for more instructions and details. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefaultVector(); + //io.Fonts->AddFontDefaultBitmap(); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); + //IM_ASSERT(font != nullptr); + + return self; +} + +-(MTKView *)mtkView +{ + return (MTKView *)self.view; +} + +-(void)loadView +{ + self.view = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, 1200, 800)]; +} + +-(void)viewDidLoad +{ + [super viewDidLoad]; + + self.mtkView.device = self.device; + self.mtkView.delegate = self; + +#if TARGET_OS_OSX + ImGui_ImplOSX_Init(self.view); + [NSApp activateIgnoringOtherApps:YES]; +#endif +} + +-(void)drawInMTKView:(MTKView*)view +{ + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize.x = view.bounds.size.width; + io.DisplaySize.y = view.bounds.size.height; + +#if TARGET_OS_OSX + CGFloat framebufferScale = view.window.screen.backingScaleFactor ?: NSScreen.mainScreen.backingScaleFactor; +#else + CGFloat framebufferScale = view.window.screen.scale ?: UIScreen.mainScreen.scale; +#endif + io.DisplayFramebufferScale = ImVec2(framebufferScale, framebufferScale); + [self.commandAllocator reset]; + + id commandBuffer = [self.device newCommandBuffer]; + [commandBuffer beginCommandBufferWithAllocator:self.commandAllocator]; + + MTL4RenderPassDescriptor* renderPassDescriptor = view.currentMTL4RenderPassDescriptor; + if (renderPassDescriptor == nil) + { + [commandBuffer endCommandBuffer]; + [self.commandQueue commit:&commandBuffer count:1]; + return; + } + + // Start the Dear ImGui frame + static int frameIndex = 0; + ImGui_ImplMetal4_NewFrame(renderPassDescriptor, frameIndex); + frameIndex++; + frameIndex = (frameIndex + 1) % FRAMES_IN_FLIGHT; + +#if TARGET_OS_OSX + ImGui_ImplOSX_NewFrame(view); +#endif + ImGui::NewFrame(); + + // Our state (make them static = more or less global) as a convenience to keep the example terse. + static bool show_demo_window = true; + static bool show_another_window = false; + static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + [renderEncoder pushDebugGroup:@"Dear ImGui rendering"]; + ImGui_ImplMetal4_RenderDrawData(draw_data, commandBuffer, renderEncoder); + [renderEncoder popDebugGroup]; + [renderEncoder endEncoding]; + + // Present + [commandBuffer endCommandBuffer]; + [self.commandQueue waitForDrawable:view.currentDrawable]; + [self.commandQueue commit:&commandBuffer count:1]; + [self.commandQueue signalDrawable:view.currentDrawable]; + [view.currentDrawable present]; +} + +-(void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size +{ +} + +//----------------------------------------------------------------------------------- +// Input processing +//----------------------------------------------------------------------------------- + +#if TARGET_OS_OSX + +- (void)viewWillAppear +{ + [super viewWillAppear]; + self.view.window.delegate = self; +} + +- (void)windowWillClose:(NSNotification *)notification +{ + ImGui_ImplMetal4_Shutdown(); + ImGui_ImplOSX_Shutdown(); + ImGui::DestroyContext(); +} + +#else + +// This touch mapping is super cheesy/hacky. We treat any touch on the screen +// as if it were a depressed left mouse button, and we don't bother handling +// multitouch correctly at all. This causes the "cursor" to behave very erratically +// when there are multiple active touches. But for demo purposes, single-touch +// interaction actually works surprisingly well. +-(void)updateIOWithTouchEvent:(UIEvent *)event +{ + UITouch *anyTouch = event.allTouches.anyObject; + CGPoint touchLocation = [anyTouch locationInView:self.view]; + ImGuiIO &io = ImGui::GetIO(); + io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); + io.AddMousePosEvent(touchLocation.x, touchLocation.y); + + BOOL hasActiveTouch = NO; + for (UITouch *touch in event.allTouches) + { + if (touch.phase != UITouchPhaseEnded && touch.phase != UITouchPhaseCancelled) + { + hasActiveTouch = YES; + break; + } + } + io.AddMouseButtonEvent(0, hasActiveTouch); +} + +-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self updateIOWithTouchEvent:event]; } +-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [self updateIOWithTouchEvent:event]; } +-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [self updateIOWithTouchEvent:event]; } +-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [self updateIOWithTouchEvent:event]; } + +#endif + +@end + +//----------------------------------------------------------------------------------- +// AppDelegate +//----------------------------------------------------------------------------------- + +#if TARGET_OS_OSX + +@interface AppDelegate : NSObject +@property (nonatomic, strong) NSWindow *window; +@end + +@implementation AppDelegate + +-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender +{ + return YES; +} + +-(instancetype)init +{ + if (self = [super init]) + { + NSViewController *rootViewController = [[AppViewController alloc] initWithNibName:nil bundle:nil]; + self.window = [[NSWindow alloc] initWithContentRect:NSZeroRect + styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable + backing:NSBackingStoreBuffered + defer:NO]; + self.window.contentViewController = rootViewController; + [self.window center]; + [self.window makeKeyAndOrderFront:self]; + } + return self; +} + +@end + +#else + +@interface AppDelegate : UIResponder +@property (strong, nonatomic) UIWindow *window; +@end + +@implementation AppDelegate + +-(BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + UIViewController *rootViewController = [[AppViewController alloc] init]; + self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + return YES; +} + +@end + +#endif + +//----------------------------------------------------------------------------------- +// Application main() function +//----------------------------------------------------------------------------------- + +#if TARGET_OS_OSX + +int main(int, const char**) +{ + @autoreleasepool + { + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + + AppDelegate *appDelegate = [[AppDelegate alloc] init]; // creates window + [NSApp setDelegate:appDelegate]; + + [NSApp activateIgnoringOtherApps:YES]; + [NSApp run]; + } + return 0; +} + +#else + +int main(int argc, char * argv[]) +{ + @autoreleasepool + { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} + +#endif From 9bb131fb2f866dba6be12c3a73023e984e18a959 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 8 Jul 2026 18:28:37 +0200 Subject: [PATCH 02/19] CI: enable Metal4 examples. (#9466, #9451) (Untested) --- .github/workflows/build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7e1095c9..540578ef4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -509,7 +509,7 @@ jobs: run: g++ -c -I. -std=c++11 -DIMGUI_IMPL_VULKAN_NO_PROTOTYPES=1 backends/imgui_impl_vulkan.cpp Build-MacOS: - runs-on: macos-latest + runs-on: macos-26 name: Build - MacOS defaults: @@ -575,8 +575,8 @@ jobs: - name: Build macOS example_sdl2_metal run: make -C examples/example_sdl2_metal - #- name: Build macOS example_sdl3_metal4 - # run: make -C examples/example_sdl3_metal4 + - name: Build macOS example_sdl3_metal4 + run: make -C examples/example_sdl3_metal4 - name: Build macOS example_sdl2_opengl2 run: make -C examples/example_sdl2_opengl2 @@ -593,9 +593,9 @@ jobs: run: xcodebuild -project examples/example_apple_metal/example_apple_metal.xcodeproj -target example_apple_metal_macos if: github.event_name == 'workflow_run' - #- name: Build macOS example_apple_metal4 - # run: xcodebuild -project examples/example_apple_metal4/example_apple_metal4.xcodeproj -target example_apple_metal_macos - # if: github.event_name == 'workflow_run' + - name: Build macOS example_apple_metal4 + run: xcodebuild -project examples/example_apple_metal4/example_apple_metal4.xcodeproj -target example_apple_metal_macos + if: github.event_name == 'workflow_run' - name: Build macOS example_apple_opengl2 run: xcodebuild -project examples/example_apple_opengl2/example_apple_opengl2.xcodeproj -target example_osx_opengl2 From da137cbbb066e57f4b44f3f331c36e1e30e1cbe2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 8 Jul 2026 18:40:29 +0200 Subject: [PATCH 03/19] (Breaking) Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload`. Use `ImGuiDragDropFlags_PayloadAutoExpire`. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 1 + imgui.h | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 373da111f..ed02e3f0b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -45,6 +45,8 @@ Breaking Changes: was obsoleted in 1.90.7 (May 2024). Use `ImGuiTreeNodeFlags_SpanLabelWidth`. - ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. +- Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload` + which obsoleted in 1.90.9 (July 2024). Use `ImGuiDragDropFlags_PayloadAutoExpire`. Other Changes: diff --git a/imgui.cpp b/imgui.cpp index 2954bd6eb..47c938977 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -395,6 +395,7 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2026/07/08 (1.92.9) - Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload` which obsoleted in 1.90.9 (July 2024). Use `ImGuiDragDropFlags_PayloadAutoExpire`. - 2026/07/06 (1.92.9) - ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in favor of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. - 2026/06/02 (1.92.9) - TreeNode: commented out legacy name ImGuiTreeNodeFlags_SpanTextWidth which was obsoleted in 1.90.7 (May 2024). Use ImGuiTreeNodeFlags_SpanLabelWidth instead. - 2026/05/07 (1.92.8) - DrawList: swapped the last two arguments of AddRect(), AddPolyline(), PathStroke(). diff --git a/imgui.h b/imgui.h index d63544c12..1fa433837 100644 --- a/imgui.h +++ b/imgui.h @@ -1503,7 +1503,7 @@ enum ImGuiDragDropFlags_ ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect, // For peeking ahead and inspecting the payload before delivery. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGuiDragDropFlags_SourceAutoExpirePayload = ImGuiDragDropFlags_PayloadAutoExpire, // Renamed in 1.90.9 + //ImGuiDragDropFlags_SourceAutoExpirePayload = ImGuiDragDropFlags_PayloadAutoExpire, // Renamed in 1.90.9 #endif }; From b62bfd6b06de958e4630b715225b7e8409bfd0f9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 10 Jul 2026 21:36:43 +0200 Subject: [PATCH 04/19] InputText: reworked `io.ConfigInputTextEnterKeepActive` mode so that pressing Ctrl+Enter or Shift+Enter still allows to deactivate. cc #9239 --- docs/CHANGELOG.txt | 2 ++ imgui.h | 2 +- imgui_widgets.cpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ed02e3f0b..8214f0548 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -63,6 +63,8 @@ Other Changes: - InputText: - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) This is automatically scaled by `style.ScaleAllSizes()`. + - Reworked `io.ConfigInputTextEnterKeepActive` mode so that pressing + Ctrl+Enter or Shift+Enter still allows to deactivate. (#9239) - Tables: - Redesigned/rewrote code to reconcile columns and settings on topology changes. (#9108) - When a column label is passed to TableSetupColumn(), the underlying identifier diff --git a/imgui.h b/imgui.h index 1fa433837..0216b893b 100644 --- a/imgui.h +++ b/imgui.h @@ -2442,7 +2442,7 @@ struct ImGuiIO bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // Swap Cmd<>Ctrl keys + OS X style text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl. bool ConfigInputTrickleEventQueue; // = true // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates. bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). - bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). + bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). Ctrl+Enter or Shift+Enter will deactivate normally. ImGuiColorEditFlags ConfigColorEditFlags; // = // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c787a0bbd..11b7da0b8 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5173,7 +5173,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!is_new_line) { validated = clear_active_id = true; - if (io.ConfigInputTextEnterKeepActive && !is_multiline) + if (io.ConfigInputTextEnterKeepActive && !is_multiline && !is_ctrl_enter && !is_shift_enter) { // Queue reactivation, so that e.g. IsItemDeactivatedAfterEdit() will work. (#9001) state->SelectAll(); // No need to scroll From 003ee19d9e6203b5c68467c0e6c86d873c125e45 Mon Sep 17 00:00:00 2001 From: ShiroKSH Date: Mon, 13 Jul 2026 21:28:48 +0300 Subject: [PATCH 05/19] Backends: SDL_Renderer3: fixed default sampler not being Linear. (#7616, #9470, #9378) --- backends/imgui_impl_sdlrenderer3.cpp | 5 +++++ docs/CHANGELOG.txt | 2 ++ 2 files changed, 7 insertions(+) diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index bf0250401..de9eae442 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -25,6 +25,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-07-15: Fixed default sampler state to be linear (broken 2026-04-23). (#9470, #9378) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-03-12: Fixed invalid assert in ImGui_ImplSDLRenderer3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. @@ -77,10 +78,14 @@ static ImGui_ImplSDLRenderer3_Data* ImGui_ImplSDLRenderer3_GetBackendData() // Functions static void ImGui_ImplSDLRenderer3_SetupRenderState(SDL_Renderer* renderer) { + ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); + // Clear out any viewports and cliprect set by the user // FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process. SDL_SetRenderViewport(renderer, nullptr); SDL_SetRenderClipRect(renderer, nullptr); + + bd->CurrentScaleMode = SDL_SCALEMODE_LINEAR; } void ImGui_ImplSDLRenderer3_NewFrame() diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 8214f0548..9fa7972a9 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -158,6 +158,8 @@ Other Changes: Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] - Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to dynamically select between use of glBindSampler() and glTexParameter(). (#9378) + - SDLRenderer3: + - Fixed default sampler not being Linear. Regression in 1.92.8. (#7616, #9470, #9378) [@ShiroKSH] - Win32: - Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()` when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends From bad1ee71f89103e6e9f08b761d35548ef7034555 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 13:42:21 +0200 Subject: [PATCH 06/19] Backends: SDL_Renderer3: fixed changing scale mode not actually working on all platforms. (#7616, #9470, #9378) --- backends/imgui_impl_sdlrenderer3.cpp | 4 ++++ docs/CHANGELOG.txt | 1 + 2 files changed, 5 insertions(+) diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index de9eae442..d4c043cbc 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -171,6 +171,7 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = render_scale; + SDL_ScaleMode last_scale_mode = bd->CurrentScaleMode; // Render command lists for (const ImDrawList* draw_list : draw_data->CmdLists) @@ -208,6 +209,9 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* const float* uv = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, uv)); const SDL_Color* color = (const SDL_Color*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, col)); // SDL 2.0.19+ + if (last_scale_mode != bd->CurrentScaleMode) + SDL_FlushRenderer(renderer); + // Bind texture, Draw SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID(); SDL_SetTextureScaleMode(tex, bd->CurrentScaleMode); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9fa7972a9..935ff19ea 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -159,6 +159,7 @@ Other Changes: - Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to dynamically select between use of glBindSampler() and glTexParameter(). (#9378) - SDLRenderer3: + - Fixed sampler change which didn't work on all graphics backends. (#7616, #9470, #9378) - Fixed default sampler not being Linear. Regression in 1.92.8. (#7616, #9470, #9378) [@ShiroKSH] - Win32: - Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()` From addbfa50214f1e2302a1439b10c722cab35c2c0c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 13:57:48 +0200 Subject: [PATCH 07/19] Settings: fixed ConfigIniSettingsAutoDiscardMonths not calling Cleanup (#9460, #9471) --- imgui.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index 47c938977..d8e790ea1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15809,7 +15809,10 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) // Call post-read handlers ImGuiSettingsCleanupArgs cleanup_args; if (g.IO.ConfigIniSettingsAutoDiscardMonths > 0) + { cleanup_args.DiscardOlderThanMonths = g.IO.ConfigIniSettingsAutoDiscardMonths; + CleanupIniSettings(&cleanup_args); + } for (ImGuiSettingsHandler& handler : g.SettingsHandlers) if (handler.ApplyAllFn != NULL) handler.ApplyAllFn(&g, &handler); From 0720d59bb734901f3c6bf760341fedac83868fb1 Mon Sep 17 00:00:00 2001 From: ShiroKSH Date: Mon, 13 Jul 2026 21:34:14 +0300 Subject: [PATCH 08/19] Tables: validate ini settings column count. (#9472) + Amend with comment by ocornut. --- imgui_tables.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index e1328fe59..efc03d7d9 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -4165,9 +4165,12 @@ static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandle static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) { + // FIXME: As topology changes are allowed, strictly speaking a >= IMGUI_TABLE_MAX_COLUMNS tables stored in .ini file + // that was emitted with a higher max count could still be meaningful in some unlikely cases. + // We might want to use another MAX defined as (1<<(sizeof(ImGuiTableColumnIdx)*8-1))-1. ImGuiID id = 0; int columns_count = 0; - if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2) + if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2 || columns_count <= 0 || columns_count >= IMGUI_TABLE_MAX_COLUMNS) return NULL; return ImGui::TableSettingsCreate(id, columns_count); } From 0302703a7dc290dbed3a312a1fb288e8e07d1232 Mon Sep 17 00:00:00 2001 From: Turtle Dove Date: Wed, 15 Jul 2026 07:36:59 -0500 Subject: [PATCH 09/19] CI: Android: Gradle 9.4.1 via setup-gradle action for AGP 9.2.0 (#8878) (#9467) --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 540578ef4..5bbaefd1a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -661,10 +661,10 @@ jobs: name: Build - Android steps: - #- name: Setup Gradle - # uses: gradle/actions/setup-gradle@v6 - # with: - # gradle-version: '8.14.5' + - name: Setup Gradle 9.4.1 + uses: gradle/actions/setup-gradle@v6 + with: + gradle-version: '9.4.1' - uses: actions/checkout@v6 - name: Build example_android_opengl3 From 909082890a404ceba3f4b0f624db73f04fc9288a Mon Sep 17 00:00:00 2001 From: Turtle Dove Date: Wed, 15 Jul 2026 09:37:12 -0500 Subject: [PATCH 10/19] Backends: Android: clear mouse position on touch release. (#6627, #9474) --- backends/imgui_impl_android.cpp | 5 ++++- docs/CHANGELOG.txt | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_android.cpp b/backends/imgui_impl_android.cpp index a76de1c26..f324c7dce 100644 --- a/backends/imgui_impl_android.cpp +++ b/backends/imgui_impl_android.cpp @@ -23,7 +23,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). +// 2026-07-15: Inputs: clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent items from staying in hovered state. (#6627, #9474) +// 2022-09-26: Inputs: renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). // 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion. // 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+). // 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range. @@ -230,6 +231,8 @@ int32_t ImGui_ImplAndroid_HandleInputEvent(const AInputEvent* input_event) { io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index)); io.AddMouseButtonEvent(0, event_action == AMOTION_EVENT_ACTION_DOWN); + if (event_action == AMOTION_EVENT_ACTION_UP) // (#6627, #9474) + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); } break; } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 935ff19ea..05aaa3162 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -150,6 +150,9 @@ Other Changes: - Misc: - Added IM_DEBUG_BREAK() handler for GCC+AArch64/ARM64. [@tom-seddon] - Backends: + - Android: + - Clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent + items from staying in hovered state. (#6627, #9474) [@Turtle-PB] - Metal4: - Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich] - Added Metal-cpp support enabled with `IMGUI_IMPL_METAL_CPP` define. (#9461) [@MERL10N] From 35a7e8e864ac3a7f8a3869029ff48d404fea56bb Mon Sep 17 00:00:00 2001 From: Turtle Dove Date: Wed, 15 Jul 2026 09:37:12 -0500 Subject: [PATCH 11/19] Backends: SDL2: restore SDL_StartTextInput()/SDL_StopTextInput() calls on Android (#7636, #9474) --- backends/imgui_impl_android.cpp | 1 + backends/imgui_impl_sdl2.cpp | 13 +++++++++++++ docs/CHANGELOG.txt | 7 +++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_android.cpp b/backends/imgui_impl_android.cpp index f324c7dce..08b3de08a 100644 --- a/backends/imgui_impl_android.cpp +++ b/backends/imgui_impl_android.cpp @@ -24,6 +24,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2026-07-15: Inputs: clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent items from staying in hovered state. (#6627, #9474) +// 2023-04-11: Inputs: calling new io.AddMouseSourceEvent() to discriminate Mouse from Touch events. // 2022-09-26: Inputs: renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). // 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion. // 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+). diff --git a/backends/imgui_impl_sdl2.cpp b/backends/imgui_impl_sdl2.cpp index 4def68dd6..eec4e0075 100644 --- a/backends/imgui_impl_sdl2.cpp +++ b/backends/imgui_impl_sdl2.cpp @@ -21,6 +21,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-07-15: Inputs: restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard support on Android/mobile. (#7636, #9474) // 2026-04-16: Made ImGui_ImplSDL2_GetContentScaleForWindow(), ImGui_ImplSDL2_GetContentScaleForDisplay() helpers return a minimum of 1.0f, as some Linux setup seems to report <1.0f value and this breaks scaling border size. (#9369) // 2026-02-13: Inputs: systems other than X11 are back to starting mouse capture on mouse down (reverts 2025-02-26 change). Only X11 requires waiting for a drag by default (not ideal, but a better default for X11 users). Added ImGui_ImplSDL2_SetMouseCaptureMode() for X11 debugger users. (#3650, #6410, #9235) // 2026-01-15: Changed GetClipboardText() handler to return nullptr on error aka clipboard contents is not text. Consistent with other backends. (#9168) @@ -205,6 +206,18 @@ static void ImGui_ImplSDL2_PlatformSetImeData(ImGuiContext*, ImGuiViewport*, ImG r.w = 1; r.h = (int)data->InputLineHeight; SDL_SetTextInputRect(&r); + +#ifdef __ANDROID__ + // SDL_StartTextInput() is needed on Android (and some other platforms) to show the on-screen keyboard. (#7636, #9474, #6306) + // It was removed in a7703fe6 due to concerns about its relation to desktop IME, but is required on mobile. + SDL_StartTextInput(); +#endif + } + else + { +#ifdef __ANDROID__ + SDL_StopTextInput(); +#endif } } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 05aaa3162..f4f699220 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -151,8 +151,8 @@ Other Changes: - Added IM_DEBUG_BREAK() handler for GCC+AArch64/ARM64. [@tom-seddon] - Backends: - Android: - - Clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent - items from staying in hovered state. (#6627, #9474) [@Turtle-PB] + - Clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent + items from staying in hovered state. (#6627, #9474) [@Turtle-PB] - Metal4: - Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich] - Added Metal-cpp support enabled with `IMGUI_IMPL_METAL_CPP` define. (#9461) [@MERL10N] @@ -161,6 +161,9 @@ Other Changes: Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] - Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to dynamically select between use of glBindSampler() and glTexParameter(). (#9378) + - SDL2: + - Restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard + support on Android. (#7636, #9474) [@Turtle-PB] - SDLRenderer3: - Fixed sampler change which didn't work on all graphics backends. (#7616, #9470, #9378) - Fixed default sampler not being Linear. Regression in 1.92.8. (#7616, #9470, #9378) [@ShiroKSH] From e3927dc004a05f4cc7b4ebe5f1fe204efbbb4f29 Mon Sep 17 00:00:00 2001 From: Turtle Dove Date: Wed, 15 Jul 2026 07:58:39 -0500 Subject: [PATCH 12/19] Backends: OpenGL2/3: Backup and restore GL_UNPACK state in UpdateTexture() (#9473, #8802) --- backends/imgui_impl_opengl2.cpp | 11 +++++++++++ backends/imgui_impl_opengl3.cpp | 20 +++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 6cbaaac77..23afa1d33 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -30,6 +30,7 @@ // 2026-03-12: OpenGL: Fixed invalid assert in ImGui_ImplOpenGL3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures. (#8802) +// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL2_CreateFontsTexture() and ImGui_ImplOpenGL2_DestroyFontsTexture(). // 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-06-28: OpenGL: ImGui_ImplOpenGL2_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL2_DestroyFontsTexture(). (#7748) @@ -262,6 +263,12 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) { + // Backup GL_UNPACK state that we modify, restore on exit. + // This prevents corrupting the caller's pixel store state when ImGui + // creates or updates textures mid-frame. (#8802, #9473) + GLint last_unpack_row_length = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); + GLint last_unpack_alignment = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); + if (tex->Status == ImTextureStatus_WantCreate) { // Create and upload new texture to graphics system @@ -318,6 +325,10 @@ void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) tex->SetTexID(ImTextureID_Invalid); tex->SetStatus(ImTextureStatus_Destroyed); } + + // Restore GL_UNPACK state + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack_alignment)); } bool ImGui_ImplOpenGL2_CreateDeviceObjects() diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 1f96c6fe8..8e21a3a5f 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -32,6 +32,7 @@ // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-07-22: OpenGL: Add and call embedded loader shutdown during ImGui_ImplOpenGL3_Shutdown() to facilitate multiple init/shutdown cycles in same process. (#8792) // 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures (#8802) + restore non-WebGL/ES update path that doesn't require a CPU-side copy. +// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL3_CreateFontsTexture() and ImGui_ImplOpenGL3_DestroyFontsTexture(). // 2025-06-04: OpenGL: Made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor GL_PRIMITIVE_RESTART. (#8664) // 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406) @@ -666,7 +667,16 @@ static void ImGui_ImplOpenGL3_DestroyTexture(ImTextureData* tex) void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) { - // FIXME: Consider backing up and restoring + // Backup GL_UNPACK state that we modify, restore on exit. + // This prevents corrupting the caller's pixel store state when ImGui + // creates or updates textures mid-frame. (#8802, #9473) +#ifdef GL_UNPACK_ROW_LENGTH + GLint last_unpack_row_length = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); +#endif +#ifdef GL_UNPACK_ALIGNMENT + GLint last_unpack_alignment = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); +#endif + if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) { #ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES @@ -738,6 +748,14 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) ImGui_ImplOpenGL3_DestroyTexture(tex); + + // Restore GL_UNPACK state +#ifdef GL_UNPACK_ROW_LENGTH + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); +#endif +#ifdef GL_UNPACK_ALIGNMENT + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack_alignment)); +#endif } // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. From 0ff4a8a9008e38759f2e4273ff90643d1d75c17d Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 18:19:07 +0200 Subject: [PATCH 13/19] Backends: OpenGL2/3: Backup and restore GL_UNPACK state in UpdateTexture(). Amends. (#9473, #8802) Fix setting GL_UNPACK_ROW_LENGTH twice. --- backends/imgui_impl_opengl2.cpp | 4 +--- backends/imgui_impl_opengl3.cpp | 31 +++++++++++++------------------ docs/CHANGELOG.txt | 5 +++++ 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 23afa1d33..111f37413 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -26,11 +26,11 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-03-12: OpenGL: Fixed invalid assert in ImGui_ImplOpenGL3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures. (#8802) -// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL2_CreateFontsTexture() and ImGui_ImplOpenGL2_DestroyFontsTexture(). // 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-06-28: OpenGL: ImGui_ImplOpenGL2_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL2_DestroyFontsTexture(). (#7748) @@ -264,8 +264,6 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) { // Backup GL_UNPACK state that we modify, restore on exit. - // This prevents corrupting the caller's pixel store state when ImGui - // creates or updates textures mid-frame. (#8802, #9473) GLint last_unpack_row_length = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); GLint last_unpack_alignment = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 8e21a3a5f..f1f2bc93c 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -23,6 +23,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-06-17: OpenGL: Expose selected render state in ImGui_ImplOpenGL3_RenderState, Allowing to dynamically select between use of glBindSampler() and glTexParameter(). You can access in 'void* platform_io.Renderer_RenderState' during rendering. // 2026-06-03: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) @@ -32,7 +33,6 @@ // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-07-22: OpenGL: Add and call embedded loader shutdown during ImGui_ImplOpenGL3_Shutdown() to facilitate multiple init/shutdown cycles in same process. (#8792) // 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures (#8802) + restore non-WebGL/ES update path that doesn't require a CPU-side copy. -// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL3_CreateFontsTexture() and ImGui_ImplOpenGL3_DestroyFontsTexture(). // 2025-06-04: OpenGL: Made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor GL_PRIMITIVE_RESTART. (#8664) // 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406) @@ -668,21 +668,13 @@ static void ImGui_ImplOpenGL3_DestroyTexture(ImTextureData* tex) void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) { // Backup GL_UNPACK state that we modify, restore on exit. - // This prevents corrupting the caller's pixel store state when ImGui - // creates or updates textures mid-frame. (#8802, #9473) -#ifdef GL_UNPACK_ROW_LENGTH - GLint last_unpack_row_length = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); -#endif -#ifdef GL_UNPACK_ALIGNMENT - GLint last_unpack_alignment = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); -#endif - + GLint last_unpack_row_length = 0; (void)last_unpack_row_length; + GLint last_unpack_alignment = 0; (void)last_unpack_alignment; if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) { #ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES - GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); -#endif -#ifdef GL_UNPACK_ALIGNMENT + GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); + GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); #endif } @@ -706,6 +698,9 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); +#if GL_UNPACK_ROW_LENGTH // Not on WebGL/ES + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); +#endif GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->Width, tex->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); // Store identifiers @@ -728,7 +723,6 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->Width)); for (ImTextureRect& r : tex->Updates) GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixelsAt(r.x, r.y))); - GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); #else // GL ES doesn't have GL_UNPACK_ROW_LENGTH, so we need to (A) copy to a contiguous buffer or (B) upload line by line. ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); @@ -750,12 +744,13 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) ImGui_ImplOpenGL3_DestroyTexture(tex); // Restore GL_UNPACK state + if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) + { #ifdef GL_UNPACK_ROW_LENGTH - GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); -#endif -#ifdef GL_UNPACK_ALIGNMENT - GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack_alignment)); + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack_alignment)); #endif + } } // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f4f699220..d539a3c3e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -156,11 +156,16 @@ Other Changes: - Metal4: - Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich] - Added Metal-cpp support enabled with `IMGUI_IMPL_METAL_CPP` define. (#9461) [@MERL10N] + - OpenGL2: + - Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT when updating texture + to avoid altering caller GL state. (#8802, #9473) [@Turtle-PB] - OpenGL3: - GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] - Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to dynamically select between use of glBindSampler() and glTexParameter(). (#9378) + - Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT when updating texture + to avoid altering caller GL state. (#8802, #9473) [@Turtle-PB] - SDL2: - Restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard support on Android. (#7636, #9474) [@Turtle-PB] From baeea39c1a09ff811728c22e3f35068f1e86154d Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 10 Jul 2026 16:32:07 +0200 Subject: [PATCH 14/19] Settings: fixed issues caliing ClearWindowSettings() followed by LoadIniSettingsFromMemory(). While not done with core, this would be useful to load .ini over a clean slate. --- imgui.cpp | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d8e790ea1..7e82a5ed8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6710,12 +6710,34 @@ ImGuiWindow* ImGui::FindWindowByName(const char* name) static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) { - window->Pos = ImTrunc(ImVec2(settings->Pos.x, settings->Pos.y)); - if (settings->Size.x > 0 && settings->Size.y > 0) - window->Size = window->SizeFull = ImTrunc(ImVec2(settings->Size.x, settings->Size.y)); - window->Collapsed = settings->Collapsed; + if (settings != NULL) + { + window->Pos = ImTrunc(ImVec2(settings->Pos.x, settings->Pos.y)); + if (settings->Size.x > 0 && settings->Size.y > 0) + { + window->Size = window->SizeFull = ImTrunc(ImVec2(settings->Size.x, settings->Size.y)); + window->AutoFitFramesX = window->AutoFitFramesY = 0; + } + window->Collapsed = settings->Collapsed; + SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); + } + + window->DC.CursorStartPos = window->DC.CursorMaxPos = window->DC.IdealMaxPos = window->Pos; // So first call to CalcWindowContentSizes() doesn't return crazy values + if ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) + { + window->AutoFitFramesX = window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } + else + { + window->AutoFitFramesX = (window->Size.x <= 0.0f) ? 2 : 0; + window->AutoFitFramesY = (window->Size.y <= 0.0f) ? 2 : 0; + window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); + } } +// Note that 'settings' may be NULL. +// Sets _Once, _Appearing, _FirstUseEver. _FirstUseEver will be cleared again if there are settings. static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) { // Initial window state with e.g. default/arbitrary window position @@ -6725,28 +6747,9 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s window->Pos = main_viewport->Pos + ImVec2(60, 60); window->Size = window->SizeFull = ImVec2(0, 0); window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; - + ApplyWindowSettings(window, settings); if (settings != NULL) - { settings->LastUsedDate = g.SessionDate; - SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); - ApplyWindowSettings(window, settings); - } - window->DC.CursorStartPos = window->DC.CursorMaxPos = window->DC.IdealMaxPos = window->Pos; // So first call to CalcWindowContentSizes() doesn't return crazy values - - if ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) - { - window->AutoFitFramesX = window->AutoFitFramesY = 2; - window->AutoFitOnlyGrows = false; - } - else - { - if (window->Size.x <= 0.0f) - window->AutoFitFramesX = 2; - if (window->Size.y <= 0.0f) - window->AutoFitFramesY = 2; - window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); - } } static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) From 2392a52660a2e031bdacab239fd0e9073fb1563c Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 3 Apr 2026 21:34:27 +0200 Subject: [PATCH 15/19] InputText: tag InputTextDeactivatedState with an elapsing frame. Avoid unnecessary InputTextDeactivateHook() call on manual deactivation. InputTextDeactivateHook() only takes a record when Edited + callback marks edited. (#9476, #701) for _NoLiveEdit it's easier than we don't use IsItemDeactivatedAfterEdit() in InputText()'s `if (g.InputTextDeactivatedState.ID == id)` block. --- imgui.cpp | 2 ++ imgui_internal.h | 3 ++- imgui_widgets.cpp | 11 ++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 7e82a5ed8..2fbc33f6c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5686,6 +5686,8 @@ void ImGui::NewFrame() if (g.DeactivatedItemData.ElapseFrame < g.FrameCount) g.DeactivatedItemData.ID = 0; g.DeactivatedItemData.IsAlive = false; + if (g.InputTextDeactivatedState.ElapseFrame < g.FrameCount) + g.InputTextDeactivatedState.ID = 0; // Record when we have been stationary as this state is preserved while over same item. // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values. diff --git a/imgui_internal.h b/imgui_internal.h index 23b8ec0fd..b92274846 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1239,10 +1239,11 @@ struct IMGUI_API ImGuiMenuColumns struct IMGUI_API ImGuiInputTextDeactivatedState { ImGuiID ID; // widget id owning the text state (which just got deactivated) + int ElapseFrame; ImVector TextA; // text buffer ImGuiInputTextDeactivatedState() { memset((void*)this, 0, sizeof(*this)); } - void ClearFreeMemory() { ID = 0; TextA.clear(); } + void ClearFreeMemory() { ID = 0; ElapseFrame = 0; TextA.clear(); } }; // Forward declare imstb_textedit.h structure + make its main configuration define accessible diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 11b7da0b8..f1517273a 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4586,8 +4586,11 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) ImGuiInputTextState* state = &g.InputTextState; if (id == 0 || state->ID != id) return; + if (!state->EditedBefore) + return; //IMGUI_DEBUG_LOG_ACTIVEID("InputTextDeactivateHook() id = 0x%08X\n", id); g.InputTextDeactivatedState.ID = state->ID; + g.InputTextDeactivatedState.ElapseFrame = g.FrameCount + 1; if (state->Flags & ImGuiInputTextFlags_ReadOnly) { g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat. @@ -5380,6 +5383,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen); state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + state->EditedBefore = state->EditedThisFrame = true; state->CursorAnimReset(); } } @@ -5399,7 +5403,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // This is used when e.g. losing focus or tabbing out into another InputText() which may already be using the temp buffer. if (g.InputTextDeactivatedState.ID == id) { - if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0) + // The state only exists after an Edit. More-over we cannot use IsItemDeactivatedAfterEdit(). + if (g.ActiveId != id && IsItemDeactivated() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0) { apply_new_text = g.InputTextDeactivatedState.TextA.Data; apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1; @@ -5445,7 +5450,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) // Otherwise request text input ahead for next frame. if (g.ActiveId == id && clear_active_id) + { + state->ID = 0; // To avoid InputTextDeactivateHook() unnecessarily running, which wouldn't be harmful but wasteful. ClearActiveID(); + state->ID = id; + } // Render frame if (!is_multiline) From 18d63b12bef5f902b4d2349b10e874add7b62802 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 2 Apr 2026 15:57:36 +0200 Subject: [PATCH 16/19] Added ImGuiItemFlags_LiveEdit flag, and much-awaited support for disabling it. (#9476, #701) cc #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, #1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184,#5777, #6707, #6766, #8004, #8303, #8915, #9308 --- imgui.cpp | 7 +++-- imgui.h | 5 +++- imgui_internal.h | 4 +-- imgui_widgets.cpp | 76 +++++++++++++++++++++++++++++++++-------------- 4 files changed, 64 insertions(+), 28 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 2fbc33f6c..5c7f4df5c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4833,6 +4833,7 @@ void ImGui::MarkItemEdited(ImGuiID id) // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data. ImGuiContext& g = *GImGui; + //IM_ASSERT(g.LastItemData.ID == id); // Failing cases include: "widgets_inputtext_scrolling", "widgets_inputtext_multiline_status", "widgets_selectable_input" = case of e.g TempInputText() overlayed manually with different ID (#2718) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_EditedInternal; if (g.LastItemData.ItemFlags & ImGuiItemFlags_NoMarkEdited) return; @@ -4843,14 +4844,14 @@ void ImGui::MarkItemEdited(ImGuiID id) // FIXME: Can't we fully rely on LastItemData yet? g.ActiveIdHasBeenEditedThisFrame = true; g.ActiveIdHasBeenEditedBefore = true; - if (g.DeactivatedItemData.ID == id) - g.DeactivatedItemData.HasBeenEditedBefore = true; } + if (g.DeactivatedItemData.ID == id) + g.DeactivatedItemData.HasBeenEditedBefore = true; // We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343) // We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714) // FIXME: This assert is getting a bit meaningless over time. It helped detect some unusual use cases but eventually it is becoming an unnecessary restriction. - IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || g.NavJustMovedToId || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); + IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.DeactivatedItemData.ID == id || g.ActiveIdPreviousFrame == id || g.NavJustMovedToId || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); } bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) diff --git a/imgui.h b/imgui.h index 0216b893b..672f24e66 100644 --- a/imgui.h +++ b/imgui.h @@ -30,7 +30,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.92.9 WIP" -#define IMGUI_VERSION_NUM 19285 +#define IMGUI_VERSION_NUM 19286 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -1246,6 +1246,8 @@ enum ImGuiItemFlags_ ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags(). + + ImGuiItemFlags_LiveEdit = 1 << 7, // true // WIP }; // Flags for ImGui::InputText() @@ -1275,6 +1277,7 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_DisplayEmptyRefVal = 1 << 14, // InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal. ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + //ImGuiInputTextFlags_NoLiveEdit = 1 << 25, // Elide display / Alignment ImGuiInputTextFlags_ElideLeft = 1 << 17, // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only! diff --git a/imgui_internal.h b/imgui_internal.h index b92274846..2d456b98d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1006,7 +1006,7 @@ enum ImGuiItemFlagsPrivate_ ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData() ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData() - ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups, // Please don't change, use PushItemFlag() instead. + ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEdit, // Please don't change, use PushItemFlag() instead. // Obsolete //ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior @@ -3799,7 +3799,7 @@ namespace ImGui IMGUI_API void InputTextDeactivateHook(ImGuiID id); IMGUI_API bool TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); IMGUI_API bool TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min = NULL, const void* p_clamp_max = NULL); - inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return g.ActiveId == id && g.TempInputId == id; } + inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.TempInputId == id && g.ActiveId == id) || (g.InputTextDeactivatedState.ID == id); } inline ImGuiInputTextState* GetInputTextState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active IMGUI_API void SetNextItemRefVal(ImGuiDataType data_type, void* p_data); inline bool IsItemActiveAsInputText() { ImGuiContext& g = *GImGui; return g.ActiveId != 0 && g.ActiveId == g.LastItemData.ID && g.InputTextState.ID == g.LastItemData.ID; } // This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index f1517273a..e790eeeb8 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3722,8 +3722,9 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - const bool init = (g.TempInputId != id); - if (init) + const bool is_deactivated = (g.InputTextDeactivatedState.ID == id); + const bool is_active = (g.TempInputId == id); + if (!is_active && !is_deactivated) ClearActiveID(); ImVec2 backup_pos = window->DC.CursorPos; @@ -3731,13 +3732,13 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; // Using ImGuiInputTextFlags_MergedItem above will skip ItemAdd() so we poke here. bool value_changed = InputTextEx(label, NULL, buf, (int)buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_TempInput | ImGuiInputTextFlags_AutoSelectAll, callback, user_data); KeepAliveID(id); // Not done because of ImGuiInputTextFlags_TempInput - if (init) + if (!is_active && !is_deactivated) { // First frame we started displaying the InputText widget, we expect it to take the active id. IM_ASSERT(g.ActiveId == id); g.TempInputId = g.ActiveId; } - if (g.ActiveId != id) + if (is_active && g.ActiveId != id) g.TempInputId = 0; window->DC.CursorPos = backup_pos; return value_changed; @@ -3841,8 +3842,23 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data } // Apply - bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved. - bool value_changed = input_edited ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false; + bool value_changed = false; + if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit) + { + bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved. + if (input_edited) + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); + } + else + { + //g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_Edited; + if (g.DeactivatedItemData.ID == g.LastItemData.ID) + { + //g.DeactivatedItemData.HasBeenEditedBefore = false; // Will be set below by MarkItemEdited() + //if (IsItemDeactivated()) // Should be unnecessary + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); + } + } // Step buttons if (has_step_buttons) @@ -4584,7 +4600,7 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiInputTextState* state = &g.InputTextState; - if (id == 0 || state->ID != id) + if (id == 0 || state->ID != id || g.ActiveId != id) return; if (!state->EditedBefore) return; @@ -4820,7 +4836,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool user_clicked = hovered && io.MouseClicked[0]; const bool input_requested_by_nav = (g.ActiveId != id) && (g.NavActivateId == id); const bool input_requested_by_reactivate = (g.InputTextReactivateId == id); // for io.ConfigInputTextEnterKeepActive - const bool input_requested_by_user = (user_clicked) || (g.ActiveId == 0 && (flags & ImGuiInputTextFlags_TempInput)); + const bool input_requested_by_user = (user_clicked) || (g.ActiveId == 0 && (flags & ImGuiInputTextFlags_TempInput) && g.InputTextDeactivatedState.ID != id); const ImGuiID scrollbar_id = (is_multiline && state != NULL) ? GetWindowScrollbarID(draw_window, ImGuiAxis_Y) : 0; const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == scrollbar_id; const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == scrollbar_id; @@ -4852,7 +4868,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->CursorAnimReset(); // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714) - InputTextDeactivateHook(state->ID); + if (state->ID != id && state->ID == g.ActiveId && (init_make_active && g.ActiveId != id)) + InputTextDeactivateHook(state->ID); // <-- this is essentially an earlier call to what SetActiveID() would do below. // Take a copy of the initial buffer value. // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode) @@ -5389,13 +5406,27 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } } - // Will copy result string if modified. + // Write back result string if modified. // FIXME-OPT: Could mark dirty state from the stb_textedit callbacks - if (!is_readonly && strcmp(state->TextSrc, buf) != 0) + if (!is_readonly) { - apply_new_text = state->TextSrc; - apply_new_text_length = state->TextLen; - value_changed = true; + if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit) + { + // Apply when modified + if (strcmp(state->TextSrc, buf) != 0) + { + apply_new_text = state->TextSrc; + apply_new_text_length = state->TextLen; + value_changed = true; + } + } + else + { + // Apply on validation/deactivation, otherwise cancel out previous apply attempts (e.g. revert) + value_changed = ((validated || clear_active_id || revert_edit) && strcmp(state->TextSrc, buf) != 0); + apply_new_text = value_changed ? state->TextSrc : NULL; + apply_new_text_length = value_changed ? state->TextLen : 0; + } } } @@ -5403,14 +5434,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // This is used when e.g. losing focus or tabbing out into another InputText() which may already be using the temp buffer. if (g.InputTextDeactivatedState.ID == id) { - // The state only exists after an Edit. More-over we cannot use IsItemDeactivatedAfterEdit(). - if (g.ActiveId != id && IsItemDeactivated() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0) - { - apply_new_text = g.InputTextDeactivatedState.TextA.Data; - apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1; - value_changed = true; - //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text); - } + // The state only exists after an Edit. IsItemDeactivatedAfterEdit() is not valid in every code path (see "widgets_inputtext_status_noliveedit" test). + if ((g.ActiveId != id && IsItemDeactivated()) || (g.ActiveId == id && (flags & ImGuiInputTextFlags_TempInput))) + if (!is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0) + { + apply_new_text = g.InputTextDeactivatedState.TextA.Data; + apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1; + value_changed = true; + //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text); + } g.InputTextDeactivatedState.ID = 0; } From 4853f5c96b34a00dfce921270f6c0abfa06f682d Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 19:46:54 +0200 Subject: [PATCH 17/19] Internals: rename NextItemData.ItemFlags -> ItemFlagsSet --- imgui.cpp | 6 +++--- imgui_internal.h | 4 ++-- imgui_widgets.cpp | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5c7f4df5c..5eec3950a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6435,7 +6435,7 @@ bool ImGui::IsItemEdited() void ImGui::SetNextItemAllowOverlap() { ImGuiContext& g = *GImGui; - g.NextItemData.ItemFlags |= ImGuiItemFlags_AllowOverlap; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_AllowOverlap; } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -11445,7 +11445,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu g.LastItemData.ID = id; g.LastItemData.Rect = bb; g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb; - g.LastItemData.ItemFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags; + g.LastItemData.ItemFlags = g.CurrentItemFlags | g.NextItemData.ItemFlagsSet | extra_flags; g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; // Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since the former is not cleared. @@ -11479,7 +11479,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // Lightweight clear of SetNextItemXXX data. g.NextItemData.HasFlags = ImGuiNextItemDataFlags_None; - g.NextItemData.ItemFlags = ImGuiItemFlags_None; + g.NextItemData.ItemFlagsSet = ImGuiItemFlags_None; #ifdef IMGUI_ENABLE_TEST_ENGINE if (id != 0) diff --git a/imgui_internal.h b/imgui_internal.h index 2d456b98d..80e369378 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1388,7 +1388,7 @@ enum ImGuiNextItemDataFlags_ struct ImGuiNextItemData { ImGuiNextItemDataFlags HasFlags; // Called HasFlags instead of Flags to avoid mistaking this - ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap and ImGuiItemFlags_HasSelectionUserData. + ImGuiItemFlags ItemFlagsSet; // Currently only tested/used for ImGuiItemFlags_AllowOverlap and ImGuiItemFlags_HasSelectionUserData. // Members below are NOT cleared by ItemAdd() meaning they are still valid during e.g. NavProcessItem(). Always rely on HasFlags. ImGuiID FocusScopeId; // Set by SetNextItemSelectionUserData() @@ -1403,7 +1403,7 @@ struct ImGuiNextItemData ImU32 ColorMarker; // Set by SetNextItemColorMarker(). Not exposed yet, supported by DragScalar,SliderScalar and for ImGuiSliderFlags_ColorMarkers. ImGuiNextItemData() { memset((void*)this, 0, sizeof(*this)); SelectionUserData = -1; } - inline void ClearFlags() { HasFlags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! + inline void ClearFlags() { HasFlags = ImGuiNextItemDataFlags_None; ItemFlagsSet = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! }; // Status storage for the last submitted item diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index e790eeeb8..d63c5c5e0 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1325,7 +1325,7 @@ bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) if (!all_on && any_on) { ImGuiContext& g = *GImGui; - g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_MixedValue; pressed = Checkbox(label, &all_on); } else @@ -3820,7 +3820,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited. // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. - g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_NoMarkEdited; flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; const bool has_step_buttons = (p_step != NULL); @@ -5707,7 +5707,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)... Dummy(ImVec2(0.0f, text_size_y + style.FramePadding.y)); - g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; + g.NextItemData.ItemFlagsSet |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; EndChild(); item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow); @@ -6121,7 +6121,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl ImGuiIO& io = g.IO; const float width = CalcItemWidth(); - const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0; + const bool is_readonly = ((g.NextItemData.ItemFlagsSet | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0; g.NextItemData.ClearFlags(); PushID(label); @@ -8264,7 +8264,7 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect) { // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping) - g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; if (ms->IO.RangeSrcItem == selection_user_data) ms->RangeSrcPassedBy = true; //ms->PrevSubmittedItem = ms->CurrSubmittedItem; // Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select. @@ -8272,7 +8272,7 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d } else { - g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_HasSelectionUserData; } } @@ -9427,7 +9427,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // This is only done for items for the menu set and not the full parent window. const bool menuset_is_open = IsRootOfOpenMenuSet(); if (menuset_is_open) - g.NextItemData.ItemFlags |= ImGuiItemFlags_NoWindowHoverableCheck; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_NoWindowHoverableCheck; // 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(). @@ -9652,7 +9652,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut // See BeginMenuEx() for comments about this. const bool menuset_is_open = IsRootOfOpenMenuSet(); if (menuset_is_open) - g.NextItemData.ItemFlags |= ImGuiItemFlags_NoWindowHoverableCheck; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_NoWindowHoverableCheck; // 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. From c958f16059bfa5de024cd63eb244a65c5d031590 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 21:32:39 +0200 Subject: [PATCH 18/19] EndGroup: fixed reporting combined Edit when it happens at the same time as tabbing to next ActiveId. (#9476, #701) --- imgui.cpp | 9 +++++---- imgui_internal.h | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5eec3950a..24aa23355 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4250,6 +4250,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) HoveredIdIsDisabled = false; HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f; ItemUnclipByLog = false; + AnyIdHasBeenEditedThisFrame = false; ActiveId = 0; ActiveIdIsAlive = 0; ActiveIdTimer = 0.0f; @@ -4842,8 +4843,7 @@ void ImGui::MarkItemEdited(ImGuiID id) if (g.ActiveId == id || g.ActiveId == 0) { // FIXME: Can't we fully rely on LastItemData yet? - g.ActiveIdHasBeenEditedThisFrame = true; - g.ActiveIdHasBeenEditedBefore = true; + g.AnyIdHasBeenEditedThisFrame = g.ActiveIdHasBeenEditedThisFrame = g.ActiveIdHasBeenEditedBefore = true; } if (g.DeactivatedItemData.ID == id) g.DeactivatedItemData.HasBeenEditedBefore = true; @@ -5673,6 +5673,7 @@ void ImGui::NewFrame() g.LastActiveIdTimer += g.IO.DeltaTime; g.ActiveIdPreviousFrame = g.ActiveId; g.ActiveIdIsAlive = 0; + g.AnyIdHasBeenEditedThisFrame = false; g.ActiveIdHasBeenEditedThisFrame = false; g.ActiveIdIsJustActivated = false; if (g.TempInputId != 0 && g.ActiveId != g.TempInputId) @@ -11889,7 +11890,7 @@ void ImGui::BeginGroup() group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; group_data.BackupHoveredIdIsAlive = g.HoveredId != 0; group_data.BackupIsSameLine = window->DC.IsSameLine; - group_data.BackupActiveIdHasBeenEditedThisFrame = g.ActiveIdHasBeenEditedThisFrame; + group_data.BackupAnyIdHasBeenEditedThisFrame = g.AnyIdHasBeenEditedThisFrame; group_data.BackupDeactivatedIdIsAlive = g.DeactivatedItemData.IsAlive; group_data.EmitItem = true; @@ -11954,7 +11955,7 @@ void ImGui::EndGroup() g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; // Forward Edited flag - if (g.ActiveIdHasBeenEditedThisFrame && !group_data.BackupActiveIdHasBeenEditedThisFrame) + if (g.AnyIdHasBeenEditedThisFrame && !group_data.BackupAnyIdHasBeenEditedThisFrame) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; // Forward Deactivated flag diff --git a/imgui_internal.h b/imgui_internal.h index 80e369378..fdff662d0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1209,7 +1209,7 @@ struct IMGUI_API ImGuiGroupData ImVec2 BackupCurrLineSize; float BackupCurrLineTextBaseOffset; ImGuiID BackupActiveIdIsAlive; - bool BackupActiveIdHasBeenEditedThisFrame; + bool BackupAnyIdHasBeenEditedThisFrame; bool BackupDeactivatedIdIsAlive; bool BackupHoveredIdIsAlive; bool BackupIsSameLine; @@ -2312,6 +2312,7 @@ struct ImGuiContext bool HoveredIdAllowOverlap; bool HoveredIdIsDisabled; // At least one widget passed the rect test, but has been discarded by disabled flag or popup inhibit. May be true even if HoveredId == 0. bool ItemUnclipByLog; // Disable ItemAdd() clipping, essentially a memory-locality friendly copy of LogEnabled + bool AnyIdHasBeenEditedThisFrame; ImGuiID ActiveId; // Active widget ImGuiID ActiveIdIsAlive; // Active widget has been seen this frame (we can't use a bool as the ActiveId may change within the frame) float ActiveIdTimer; From 26b82926354edca5a5e09d144bb55dbf139086c8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 21:47:45 +0200 Subject: [PATCH 19/19] Split ImGuiItemFlags_LiveEdit into ImGuiItemFlags_LiveEditText, ImGuiItemFlags_LiveEditScalar. Added Demo contents. (#9476, #701) cc #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, #1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184,#5777, #6707, #6766, #8004, #8303, #8915, #9308 --- docs/CHANGELOG.txt | 39 ++++++++++++++++++++++++ imgui.cpp | 1 + imgui.h | 25 ++++++++++++--- imgui_demo.cpp | 76 ++++++++++++++++++++++++++++++++++++++++++++++ imgui_internal.h | 2 +- imgui_widgets.cpp | 7 +++-- 6 files changed, 141 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d539a3c3e..5cecbdb37 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -60,6 +60,45 @@ Other Changes: - Fixed double-click collapse toggle not owning the mouse button. If a `SetNextWindowPos()` with pivot was queued in the same frame, the second click could trigger another item in the same window. (#9439) [@Cleroth] +- Added `ImGuiItemFlags_LiveEditOnInputText` and `ImGuiItemFlags_LiveEditOnInputScalar` + flags to configure the timing of applying edits of backing variables when typing + values using a keyboard. + (#701, #9476, #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, + #1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184, + #5777, #6707, #6766, #8004, #8303, #8915, #9308) + - Until now: + - Edits where always applied immediately to backing variable, which is equivalent + to the `ImGuiItemFlags_LiveEditXXX` flags being enabled. + - Typing '123' in an integer field would output successively 1, 12 then 123. + - Most uses of `IsItemDeactivatedAfterEdit()` or `ImGuiInputTextFlags_EnterReturnsTrue` + were actually workarounds for this issue. Advanced applications would typically + use `IsItemDeactivatedAfterEdit()` to distinguish transactions. + Workarounds often required a backing store for scalar values, and there were + also a few niggles related to `IsItemDeactivatedAfterEdit()` when using +/- + buttons of an `InputInt()` widgets. + Many of those situations can now be naturally simplified by disabling + `ImGuiItemFlags_LiveEditOnInputScalar`, which is expected to become the default. + - The new flags allows disabling this behavior selectively for strings fields + such as `InputText()` vs scalar fields: `SliderInt()`, `InputFloat()`, etc. + - When LiveEdit is disabled, edits are applied when pressing enter, tabbing out, + clearing a field or deactivating due to a focus loss. + - The flag may be altered programmatically: + PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, false); // Disable for scalars + SliderInt(...); + PopItemFlag(); + PushItemFlag(ImGuiItemFlags_LiveEditOnInput, true); // Enable for all + SliderInt(...); + PopItemFlag(); + But with upcoming new defaults it is expected you shouldn't touch them much. + - Both flags currently defaults to true, which matches previous behavior. + - The expectation is that for strings/text, enabling LiveEdit is a better default. + - The expectation is that for scalars, disable LiveEdit is a better default. + - The short-term intent is to change `ImGuiItemFlags_LiveEditOnInputScalar` to + default to being disabled, as soon as we get more feedback from users (SOON). + - We intentionally are not adding `io.ConfigLiveEditXXX` fields to dictate the + default value of each `ImGuiItemFlags_LiveEditXXX`, because this is not + expected to be a user preference but a programmer/widget preferences. + Also, we strive to make the toolkit consistent. - InputText: - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) This is automatically scaled by `style.ScaleAllSizes()`. diff --git a/imgui.cpp b/imgui.cpp index 24aa23355..fc1d4d912 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6358,6 +6358,7 @@ bool ImGui::IsItemDeactivated() return g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount; } +// Since 1.92.9: consider using NoLiveEdit flags if all you need to that values are not written bo backing variable while typing. bool ImGui::IsItemDeactivatedAfterEdit() { ImGuiContext& g = *GImGui; diff --git a/imgui.h b/imgui.h index 672f24e66..dcd19a0ef 100644 --- a/imgui.h +++ b/imgui.h @@ -1238,16 +1238,29 @@ enum ImGuiChildFlags_ // (Those are shared by all submitted items) enum ImGuiItemFlags_ { - ImGuiItemFlags_None = 0, // (Default) + ImGuiItemFlags_None = 0, // Default: ImGuiItemFlags_NoTabStop = 1 << 0, // false // Disable keyboard tabbing. This is a "lighter" version of ImGuiItemFlags_NoNav. - ImGuiItemFlags_NoNav = 1 << 1, // false // Disable any form of focusing (keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls). + ImGuiItemFlags_NoNav = 1 << 1, // false // Disable any form of focusing: keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls. ImGuiItemFlags_NoNavDefaultFocus = 1 << 2, // false // Disable item being a candidate for default focus (e.g. used by title bar items). ImGuiItemFlags_ButtonRepeat = 1 << 3, // false // Any button-like behavior will have repeat mode enabled (based on io.KeyRepeatDelay and io.KeyRepeatRate values). Note that you can also call IsItemActive() after any button to tell if it is being held. ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags(). - ImGuiItemFlags_LiveEdit = 1 << 7, // true // WIP + //--------------------------------------------------------------------------------- + // LiveEdit refers to applying edits to backing variables _while_ typing. + // Widget: | Input: | w/ LiveEdit: | Output: + // InputText() | "123" | on(default) | "1" then "12" then "123" + // InputText() | "123" | off | "123" after validating or tabbing out or losing focus. + // DragFloat(), SliderInt(), etc. | "123" | on(default)* | 1 then 12 then 123 + // DragFloat(), SliderInt(), etc. | "123" | off* | 123 after validation or tabbing out or losing focus. + //--------------------------------------------------------------------------------- + // (*) The feature is currently being evaluated, and the aim is to change ImGuiItemFlags_LiveEditOnInputScalar to OFF by default. + // The legacy behavior until July 2026 was that LiveEdit was always ON for everything. + //--------------------------------------------------------------------------------- + ImGuiItemFlags_LiveEditOnInputText = 1 << 7, // true // InputText: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. + ImGuiItemFlags_LiveEditOnInputScalar = 1 << 8, // true* // DragXXX, SliderXXX, InputScalar: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. + ImGuiItemFlags_LiveEditOnInput = ImGuiItemFlags_LiveEditOnInputText | ImGuiItemFlags_LiveEditOnInputScalar, }; // Flags for ImGui::InputText() @@ -1264,7 +1277,7 @@ enum ImGuiInputTextFlags_ // Inputs ImGuiInputTextFlags_AllowTabInput = 1 << 5, // Pressing TAB input a '\t' character into the text field - ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider using IsItemDeactivatedAfterEdit() instead! + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider disabling LiveEdit! or using IsItemDeactivatedAfterEdit() instead! ImGuiInputTextFlags_EscapeClearsAll = 1 << 7, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 8, // In multi-line mode: validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). Note that Shift+Enter always enter a new line either way. @@ -1277,7 +1290,7 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_DisplayEmptyRefVal = 1 << 14, // InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal. ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). - //ImGuiInputTextFlags_NoLiveEdit = 1 << 25, + //ImGuiInputTextFlags_NoLiveEdit = 1 << 25, // Disable applying output to backing variable while typing. Same as setting ImGuiItemFlags_LiveEditOnInput to false. // Elide display / Alignment ImGuiInputTextFlags_ElideLeft = 1 << 17, // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only! @@ -1956,6 +1969,8 @@ enum ImGuiSliderFlags_ ImGuiSliderFlags_NoSpeedTweaks = 1 << 11, // Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic. ImGuiSliderFlags_ColorMarkers = 1 << 12, // DragScalarN(), SliderScalarN(): Draw R/G/B/A color markers on each component. ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange, + //ImGuiSliderFlags_LiveEditOnInput = 1 << 13, // Shortcut to enable LiveEdit for this field. + //ImGuiSliderFlags_NoLiveEditOnInput= 1 << 14, // Shortcut to disable LiveEdit for this field. ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from legacy API (obsoleted 2020-08) that has got miscast to this enum, and will trigger an assert if needed. }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b1a326570..27413b7aa 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -86,6 +86,7 @@ Index of this file: // [SECTION] DemoWindowWidgetsFonts() // [SECTION] DemoWindowWidgetsImages() // [SECTION] DemoWindowWidgetsListBoxes() +// [SECTION] DemoWindowWidgetsLiveEdit() // [SECTION] DemoWindowWidgetsMultiComponents() // [SECTION] DemoWindowWidgetsPlotting() // [SECTION] DemoWindowWidgetsProgressBars() @@ -331,6 +332,8 @@ struct ImGuiDemoWindowData // Other data bool DisableSections = false; + bool LiveEditOverride = false; + ImGuiItemFlags LiveEditFlags = ImGuiItemFlags_LiveEditOnInputText; ExampleTreeNode* DemoTree = NULL; ~ImGuiDemoWindowData() { if (DemoTree) ExampleTree_DestroyNode(DemoTree); } @@ -2009,6 +2012,44 @@ static void DemoWindowWidgetsListBoxes() } } +//----------------------------------------------------------------------------- +// [SECTION] DemoWindowWidgetsLiveEdit() +//----------------------------------------------------------------------------- + +static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data) +{ + if (ImGui::TreeNode("Live Edit Flags")) + { + IMGUI_DEMO_MARKER("Widgets/Live Edit Flgs"); + + ImGui::TextWrapped("Select whether to apply keyboard edits to backing variables _while_ typing."); + + ImGui::Checkbox("Override Live Edit Flags in Demo Window", &demo_data->LiveEditOverride); + if (!demo_data->LiveEditOverride) + demo_data->LiveEditFlags = ImGui::GetItemFlags(); + + ImGui::BeginDisabled(demo_data->LiveEditOverride == false); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiItemFlags_LiveEditOnInputText", &demo_data->LiveEditFlags, ImGuiItemFlags_LiveEditOnInputText); + ImGui::CheckboxFlags("ImGuiItemFlags_LiveEditOnInputScalar", &demo_data->LiveEditFlags, ImGuiItemFlags_LiveEditOnInputScalar); + ImGui::Unindent(); + ImGui::EndDisabled(); + + ImGui::Text("Try typing '123' and seeing effect on backing value:"); + static char str[32] = ""; + ImGui::InputText("str", str, IM_COUNTOF(str)); + ImGui::Text("Backing value: \"%s\"", str); + static int i = 0; + ImGui::InputInt("int", &i, 0, 0); + ImGui::Text("Backing value: %d", i); + static float f = 0.0f; + ImGui::SliderFloat("float", &f, 0.0f, 100.0f); + ImGui::Text("Backing value: %f", f); + + ImGui::TreePop(); + } +} + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsMultiComponents() //----------------------------------------------------------------------------- @@ -2189,10 +2230,28 @@ static void DemoWindowWidgetsQueryingStatuses() }; static int item_type = 4; static bool item_disabled = false; + static bool liveedit_flags_override = false; + static ImGuiItemFlags liveedit_flags = 0; ImGui::Combo("Item Type", &item_type, item_names, IM_COUNTOF(item_names), IM_COUNTOF(item_names)); ImGui::SameLine(); HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered()."); ImGui::Checkbox("Item Disabled", &item_disabled); + ImGui::Checkbox("Override LiveEdit:", &liveedit_flags_override); + ImGui::SameLine(); + if (!liveedit_flags_override) + liveedit_flags = ImGui::GetItemFlags(); + ImGui::BeginDisabled(liveedit_flags_override == false); + ImGui::CheckboxFlags("_LiveEditOnInput", &liveedit_flags, ImGuiItemFlags_LiveEditOnInput); + ImGui::SameLine(); + ImGui::CheckboxFlags("_LiveEditOnInputText", &liveedit_flags, ImGuiItemFlags_LiveEditOnInputText); + ImGui::SameLine(); + ImGui::CheckboxFlags("_LiveEditOnInputScalar", &liveedit_flags, ImGuiItemFlags_LiveEditOnInputScalar); + ImGui::EndDisabled(); + if (liveedit_flags_override) + { + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputText, (liveedit_flags & ImGuiItemFlags_LiveEditOnInputText) != 0); + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, (liveedit_flags & ImGuiItemFlags_LiveEditOnInputScalar) != 0); + } // Submit selected items so we can query their status in the code following it. bool ret = false; @@ -2279,6 +2338,11 @@ static void DemoWindowWidgetsQueryingStatuses() "IsItemHovered(_Tooltip) = %d", hovered_delay_none, hovered_delay_stationary, hovered_delay_short, hovered_delay_normal, hovered_delay_tooltip); + if (liveedit_flags_override) + { + ImGui::PopItemFlag(); + ImGui::PopItemFlag(); + } if (item_disabled) ImGui::EndDisabled(); @@ -4413,8 +4477,14 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) // IMGUI_DEMO_MARKER("Widgets"); const bool disable_all = demo_data->DisableSections; // The Checkbox for that is inside the "Disabled" section at the bottom + const bool override_liveedit = demo_data->LiveEditOverride; if (disable_all) ImGui::BeginDisabled(); + if (override_liveedit) + { + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputText, (demo_data->LiveEditFlags & ImGuiItemFlags_LiveEditOnInputText) != 0); + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, (demo_data->LiveEditFlags & ImGuiItemFlags_LiveEditOnInputScalar) != 0); + } DemoWindowWidgetsBasic(); DemoWindowWidgetsBullets(); @@ -4434,6 +4504,7 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) DemoWindowWidgetsFonts(); DemoWindowWidgetsImages(); DemoWindowWidgetsListBoxes(); + DemoWindowWidgetsLiveEdit(demo_data); DemoWindowWidgetsMultiComponents(); DemoWindowWidgetsPlotting(); DemoWindowWidgetsProgressBars(); @@ -4448,6 +4519,11 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) DemoWindowWidgetsTreeNodes(); DemoWindowWidgetsVerticalSliders(); + if (override_liveedit) + { + ImGui::PopItemFlag(); + ImGui::PopItemFlag(); + } if (disable_all) ImGui::EndDisabled(); } diff --git a/imgui_internal.h b/imgui_internal.h index fdff662d0..0966819e6 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1006,7 +1006,7 @@ enum ImGuiItemFlagsPrivate_ ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData() ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData() - ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEdit, // Please don't change, use PushItemFlag() instead. + ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEditOnInputText | ImGuiItemFlags_LiveEditOnInputScalar, // Please don't change, use PushItemFlag() instead. // Obsolete //ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index d63c5c5e0..efd3a64ff 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3763,6 +3763,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData. + g.LastItemData.ItemFlags = (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) ? (g.LastItemData.ItemFlags | ImGuiItemFlags_LiveEditOnInputText) : (g.LastItemData.ItemFlags & ~ImGuiItemFlags_LiveEditOnInputText); if (!TempInputText(bb, id, label, data_buf, IM_COUNTOF(data_buf), flags)) return false; @@ -3843,7 +3844,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data // Apply bool value_changed = false; - if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) { bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved. if (input_edited) @@ -4868,7 +4869,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->CursorAnimReset(); // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714) - if (state->ID != id && state->ID == g.ActiveId && (init_make_active && g.ActiveId != id)) + if (state->ID != id && state->ID == g.ActiveId && (init_make_active && g.ActiveId != id)) //-V560 InputTextDeactivateHook(state->ID); // <-- this is essentially an earlier call to what SetActiveID() would do below. // Take a copy of the initial buffer value. @@ -5410,7 +5411,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // FIXME-OPT: Could mark dirty state from the stb_textedit callbacks if (!is_readonly) { - if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText) { // Apply when modified if (strcmp(state->TextSrc, buf) != 0)